@opengeni/api-router 3.1.3-canary.38890816741001 → 3.3.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/app.js +1 -1
- package/dist/{chunk-OT56OY4V.js → chunk-6CECHDZP.js} +1569 -399
- package/dist/{chunk-OT56OY4V.js.map → chunk-6CECHDZP.js.map} +1 -1
- package/dist/index.js +50 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/external-vault-providers.d.ts +29 -0
- package/dist/interaction-secrets-vault.d.ts +2 -2
- package/dist/mcp/action-descriptions.d.ts +1 -1
- package/dist/mcp/scheduled-task-view.d.ts +4 -4
- package/dist/routes/vault-providers.d.ts +4 -2
- package/dist/sandbox/channel-a.d.ts +10 -1
- package/dist/shutdown.d.ts +14 -0
- package/dist/site-publishing/agent-data-tools.d.ts +5 -5
- package/dist/site-publishing/agent-session.d.ts +3 -2
- package/dist/site-publishing/runtime.d.ts +7 -6
- package/dist/site-publishing/service.d.ts +8 -11
- package/dist/site-publishing/session-request-authority.d.ts +3 -0
- package/dist/workspace-tool-gateway-secrets.d.ts +1 -1
- package/package.json +25 -22
- package/src/access-grant-rls.ts +1 -0
- package/src/app.ts +14 -2
- package/src/http/sse.ts +5 -1
- package/src/index.ts +13 -1
- package/src/integrations/external-vault-providers.ts +136 -0
- package/src/interaction-secrets-vault.ts +6 -2
- package/src/mcp/action-catalog.gen.ts +253 -4
- package/src/mcp/action-descriptions.ts +32 -2
- package/src/mcp/action-schema-imports.gen.ts +3 -1
- package/src/routes/browser-sessions.ts +61 -17
- package/src/routes/computer-sessions.ts +102 -39
- package/src/routes/connections.ts +17 -0
- package/src/routes/insights-usage.ts +22 -0
- package/src/routes/sessions.ts +22 -4
- package/src/routes/site-runtime.ts +62 -20
- package/src/routes/vault-providers.ts +788 -29
- package/src/routes/vaults.ts +5 -1
- package/src/sandbox/channel-a.ts +18 -1
- package/src/sandbox/metrics-ingestion.ts +1 -0
- package/src/shutdown.ts +54 -0
- package/src/site-frame-guard.ts +11 -2
- package/src/site-publishing/agent-data-tools.ts +16 -8
- package/src/site-publishing/agent-session.ts +24 -6
- package/src/site-publishing/runtime.ts +29 -7
- package/src/site-publishing/service.ts +65 -77
- package/src/site-publishing/session-request-authority.ts +14 -0
- package/src/workspace-tool-gateway-secrets.ts +9 -2
- package/src/workspace-tool-gateway.ts +6 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import type { ExternalVaultProviderFactories } from "@opengeni/core";
|
|
3
|
+
import { type PinnedFetchOptions } from "@opengeni/network";
|
|
4
|
+
import type { KeeperRuntimeOptions } from "@opengeni/vaults-keeper";
|
|
5
|
+
/** Test seams only; production passes nothing. */
|
|
6
|
+
export type NativeExternalVaultOptions = {
|
|
7
|
+
keeper?: Pick<KeeperRuntimeOptions, "queryFunction" | "fetchFile" | "sdk">;
|
|
8
|
+
/** Applies when this call creates the pool for its CLI path. */
|
|
9
|
+
protonpass?: {
|
|
10
|
+
stateRoot?: string;
|
|
11
|
+
hostEnv?: Readonly<Record<string, string | undefined>>;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Native external vault adapters that run inside this server: Keeper Secrets
|
|
16
|
+
* Manager always, Proton Pass only when the operator installed `pass-cli`
|
|
17
|
+
* (`OPENGENI_PROTON_PASS_CLI_PATH`). Both packages load through `import()`
|
|
18
|
+
* on first use (packages/vaults-keeper/test/import-guard.test.ts). Keep this
|
|
19
|
+
* file identical to apps/worker/src/external-vault-providers.ts.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createNativeExternalVaultFactories(settings: Settings, options?: NativeExternalVaultOptions): ExternalVaultProviderFactories;
|
|
22
|
+
/** Logs out every pooled Proton Pass CLI session and wipes its state (graceful shutdown). */
|
|
23
|
+
export declare function closeNativeExternalVaultProviders(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* The fetch every Keeper request and file download uses: DNS-pinned and
|
|
26
|
+
* policy-checked (no private or special-use targets). The adapter adds
|
|
27
|
+
* HTTPS-only, no redirects, deadlines, size bounds and the caller's abort.
|
|
28
|
+
*/
|
|
29
|
+
export declare function nativeKeeperFetch(settings: Settings, pinned?: Pick<PinnedFetchOptions, "dnsLookup" | "fetchImpl" | "agentFactory">): (input: string | URL, init: RequestInit) => Promise<Response>;
|
|
@@ -7,7 +7,7 @@ import type { VaultProviderRegistry } from "@opengeni/vaults";
|
|
|
7
7
|
import { type BrowserPasskeyCredentialMaterial } from "@opengeni/contracts";
|
|
8
8
|
import { type VaultService } from "@opengeni/core";
|
|
9
9
|
import { type Database } from "@opengeni/db";
|
|
10
|
-
import type { InteractionSecretsPort } from "./interaction-secrets.js";
|
|
10
|
+
import type { AttemptSecretPrincipal, InteractionSecretsPort } from "./interaction-secrets.js";
|
|
11
11
|
export declare function passkeyItemName(credential: BrowserPasskeyCredentialMaterial): string;
|
|
12
12
|
export declare function createVaultInteractionSecretsPort(deps: {
|
|
13
13
|
db: Database;
|
|
@@ -16,5 +16,5 @@ export declare function createVaultInteractionSecretsPort(deps: {
|
|
|
16
16
|
/** Defaults to a service over the same database and key. */
|
|
17
17
|
vaultService?: VaultService;
|
|
18
18
|
/** Connected vaults (1Password, host) for a workspace's acting subject. */
|
|
19
|
-
providersFor?: (workspaceId: string, subjectId: string | null) => VaultProviderRegistry;
|
|
19
|
+
providersFor?: (workspaceId: string, subjectId: string | null, principal?: AttemptSecretPrincipal) => VaultProviderRegistry;
|
|
20
20
|
}): InteractionSecretsPort;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ActionCatalogEntry } from "./action-catalog-types.js";
|
|
2
|
-
/** Actions that must carry a description (
|
|
2
|
+
/** Actions that must carry a description (subscriptions, models and vault connections). */
|
|
3
3
|
export declare const DESCRIBED_ACTION_PATH: RegExp;
|
|
4
4
|
export declare function actionDescription(entry: Pick<ActionCatalogEntry, "method" | "path">): string | undefined;
|
|
@@ -83,8 +83,8 @@ export declare function scheduledTaskMcpSummary(task: ScheduledTask): {
|
|
|
83
83
|
runMode: "existing_session" | "new_session_per_run" | "reusable_session";
|
|
84
84
|
overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
|
|
85
85
|
action: {
|
|
86
|
-
readonly sourceId?: never;
|
|
87
86
|
kind: "agent_turn";
|
|
87
|
+
sourceId?: never;
|
|
88
88
|
sourceGeneration?: never;
|
|
89
89
|
sourceLifecycleGeneration?: never;
|
|
90
90
|
destination?: never;
|
|
@@ -221,8 +221,8 @@ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
|
|
|
221
221
|
runMode: "existing_session" | "new_session_per_run" | "reusable_session";
|
|
222
222
|
overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
|
|
223
223
|
action: {
|
|
224
|
-
readonly sourceId?: never;
|
|
225
224
|
kind: "agent_turn";
|
|
225
|
+
sourceId?: never;
|
|
226
226
|
sourceGeneration?: never;
|
|
227
227
|
sourceLifecycleGeneration?: never;
|
|
228
228
|
destination?: never;
|
|
@@ -408,8 +408,8 @@ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
|
|
|
408
408
|
runMode: "existing_session" | "new_session_per_run" | "reusable_session";
|
|
409
409
|
overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
|
|
410
410
|
action: {
|
|
411
|
-
readonly sourceId?: never;
|
|
412
411
|
kind: "agent_turn";
|
|
412
|
+
sourceId?: never;
|
|
413
413
|
sourceGeneration?: never;
|
|
414
414
|
sourceLifecycleGeneration?: never;
|
|
415
415
|
destination?: never;
|
|
@@ -601,8 +601,8 @@ export declare function boundScheduledTaskMcpPage(input: {
|
|
|
601
601
|
runMode: "existing_session" | "new_session_per_run" | "reusable_session";
|
|
602
602
|
overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
|
|
603
603
|
action: {
|
|
604
|
-
readonly sourceId?: never;
|
|
605
604
|
kind: "agent_turn";
|
|
605
|
+
sourceId?: never;
|
|
606
606
|
sourceGeneration?: never;
|
|
607
607
|
sourceLifecycleGeneration?: never;
|
|
608
608
|
destination?: never;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Hono } from "hono";
|
|
2
|
-
import { type ApiRouteDeps, type OnePasswordConnectionMetadata, type OnePasswordVaultProviderFactory } from "@opengeni/core";
|
|
2
|
+
import { type ApiRouteDeps, type OnePasswordConnectionMetadata, type OnePasswordVaultProviderFactory, type ExternalVaultProviderFactories } from "@opengeni/core";
|
|
3
3
|
import { type VaultDbContext } from "@opengeni/db";
|
|
4
|
-
import { type VaultProvider, type VaultProviderRegistry } from "@opengeni/vaults";
|
|
4
|
+
import { type VaultProvider, type VaultProviderRegistry, type VaultProviderAuthority } from "@opengeni/vaults";
|
|
5
5
|
export declare function onePasswordFactoryFor(deps: ApiRouteDeps): OnePasswordVaultProviderFactory;
|
|
6
6
|
/**
|
|
7
7
|
* The 1Password provider for a stored connection the caller can see, plus its
|
|
@@ -23,7 +23,9 @@ export declare function onePasswordProviderForConnection(deps: ApiRouteDeps, inp
|
|
|
23
23
|
export declare function vaultProviderRegistryFor(deps: ApiRouteDeps, input: {
|
|
24
24
|
workspaceId: string;
|
|
25
25
|
subjectId: string | null;
|
|
26
|
+
executionContext?: Pick<VaultProviderAuthority, "sessionId" | "turnId" | "attemptId" | "siteId">;
|
|
26
27
|
}): VaultProviderRegistry;
|
|
28
|
+
export declare function externalVaultFactoriesFor(deps: ApiRouteDeps): ExternalVaultProviderFactories;
|
|
27
29
|
export type LinkedVaultLookup = (input: {
|
|
28
30
|
workspaceId: string;
|
|
29
31
|
connectionId: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Settings } from "@opengeni/config";
|
|
2
2
|
import type { AccessGrant, Session } from "@opengeni/contracts";
|
|
3
|
-
import { type Database, type LeaseSnapshot } from "@opengeni/db";
|
|
3
|
+
import { type Database, type EnrollmentRecord, type LeaseSnapshot } from "@opengeni/db";
|
|
4
4
|
import { type EventBus } from "@opengeni/events";
|
|
5
5
|
import { type Observability } from "@opengeni/observability";
|
|
6
6
|
import type { ObjectStorage } from "@opengeni/storage";
|
|
@@ -56,6 +56,15 @@ export type ChannelAHandle = {
|
|
|
56
56
|
* who started an agent's turn, or the calling person. Routes that look up
|
|
57
57
|
* the routed machine again must use it rather than the raw caller grant. */
|
|
58
58
|
machineAuthority: () => Promise<ChannelAMachineAuthority>;
|
|
59
|
+
/** For a machine-home request, the Connected Machine and live runner this
|
|
60
|
+
* request was pinned to under `machineAuthority`. The routing session fails
|
|
61
|
+
* rather than follow a changed pointer, so a route that resolved this same
|
|
62
|
+
* sandbox can use it instead of looking the machine up again. */
|
|
63
|
+
pinnedMachine: {
|
|
64
|
+
sandboxId: string;
|
|
65
|
+
sandboxWorkspaceId: string;
|
|
66
|
+
enrollment: EnrollmentRecord;
|
|
67
|
+
} | null;
|
|
59
68
|
requestId: string;
|
|
60
69
|
};
|
|
61
70
|
export type EstablishedHandleCacheKind = "read" | "process" | "none";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type ShutdownSignal = "SIGINT" | "SIGTERM";
|
|
2
|
+
type ShutdownProcess = {
|
|
3
|
+
on(signal: ShutdownSignal, handler: () => void): unknown;
|
|
4
|
+
off(signal: ShutdownSignal, handler: () => void): unknown;
|
|
5
|
+
exit(code: number): void;
|
|
6
|
+
};
|
|
7
|
+
/** Standalone process boundary; embedded hosts keep owning their explicit close(). */
|
|
8
|
+
export declare function installApiShutdownHandlers(input: {
|
|
9
|
+
close: () => Promise<void>;
|
|
10
|
+
process?: ShutdownProcess;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
onFailure?: () => void;
|
|
13
|
+
}): () => void;
|
|
14
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type FirstPartyMcpToolName, type SiteLimitProfileKind } from "@opengeni/contracts";
|
|
2
|
-
import type
|
|
1
|
+
import { type FirstPartyMcpToolName, type Permission, type SiteLimitProfileKind } from "@opengeni/contracts";
|
|
2
|
+
import { type ApiRouteDeps } from "@opengeni/core";
|
|
3
3
|
import { type SiteRuntimeConversationViewerKind } from "@opengeni/db";
|
|
4
4
|
import type { PublishedSiteDataViewer } from "../site-data/service.js";
|
|
5
5
|
import { type SiteRuntimeHost } from "./runtime.js";
|
|
@@ -21,8 +21,6 @@ import { type SiteRuntimeHost } from "./runtime.js";
|
|
|
21
21
|
export declare const SITE_AGENT_DATA_TOOLS: readonly ["site_data_query", "site_data_write"];
|
|
22
22
|
export declare const SITE_AGENT_SQL_TOOLS: readonly ["site_sql_run"];
|
|
23
23
|
export declare const SITE_AGENT_MEMBER_SQL_TOOLS: readonly ["site_sql_query"];
|
|
24
|
-
/** The first-party permissions those tools need (Site Data writes need publish). */
|
|
25
|
-
export declare const SITE_AGENT_DATA_TOOL_PERMISSIONS: readonly ["artifacts:read", "artifacts:publish"];
|
|
26
24
|
export declare const SITE_AGENT_OTHER_SITE_MESSAGE = "This Site's assistant can only use this Site's data";
|
|
27
25
|
export declare const SITE_AGENT_MANAGEMENT_MESSAGE = "This Site's assistant can use the Site's data, not manage the Site";
|
|
28
26
|
/** The data tools one viewer's Site agent conversation gets, within the deployment's tools. */
|
|
@@ -32,6 +30,8 @@ export declare function siteAgentDataToolNames(input: {
|
|
|
32
30
|
siteSql: boolean;
|
|
33
31
|
/** The deployment's first-party tool allowlist. */
|
|
34
32
|
allowed: readonly string[];
|
|
33
|
+
/** Live member ceiling; omitted only where the native caller filters tools itself. */
|
|
34
|
+
memberPermissions?: readonly Permission[];
|
|
35
35
|
}): FirstPartyMcpToolName[];
|
|
36
36
|
/** Whether Site SQL is offered on this deployment and turned on for the Site. */
|
|
37
37
|
export declare function siteAgentSiteSqlOn(deps: Pick<ApiRouteDeps, "db" | "settings">, scope: {
|
|
@@ -42,7 +42,7 @@ export declare function siteAgentSiteSqlOn(deps: Pick<ApiRouteDeps, "db" | "sett
|
|
|
42
42
|
export declare function siteAgentDataToolsForStart(deps: Pick<ApiRouteDeps, "db" | "settings">, scope: {
|
|
43
43
|
accountId: string;
|
|
44
44
|
workspaceId: string;
|
|
45
|
-
}, siteId: string, viewerKind: SiteRuntimeConversationViewerKind): Promise<FirstPartyMcpToolName[]>;
|
|
45
|
+
}, siteId: string, viewerKind: SiteRuntimeConversationViewerKind, memberPermissions?: readonly Permission[]): Promise<FirstPartyMcpToolName[]>;
|
|
46
46
|
/** This Site's viewer (`user:<subject>` or `guest:<visitor>`) as Site Data and Site SQL see it. */
|
|
47
47
|
export declare function sitePublishedDataViewer(host: Pick<SiteRuntimeHost, "scope" | "site">, principal: string, guest: boolean): PublishedSiteDataViewer;
|
|
48
48
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { type Permission } from "@opengeni/contracts";
|
|
1
2
|
import { type ApiRouteDeps } from "@opengeni/core";
|
|
2
3
|
import type { SiteRuntimeHost } from "./runtime.js";
|
|
3
4
|
import type { SiteRuntimeViewer } from "./viewer.js";
|
|
4
|
-
export declare const SITE_AGENT_DEFAULTS_VERSION =
|
|
5
|
+
export declare const SITE_AGENT_DEFAULTS_VERSION = 2;
|
|
5
6
|
/**
|
|
6
7
|
* Before admitting more work, converge pre-redesign published-host sessions to
|
|
7
8
|
* the same Site-local tools as a new conversation. This is a trusted host
|
|
@@ -13,4 +14,4 @@ export declare const SITE_AGENT_DEFAULTS_VERSION = 1;
|
|
|
13
14
|
* audit event and completion marker atomic. Failed writes remain retryable.
|
|
14
15
|
* Existing attempts keep their accepted execution policy; no turn is rewritten.
|
|
15
16
|
*/
|
|
16
|
-
export declare function normalizeSiteAgentSession(deps: Pick<ApiRouteDeps, "db" | "settings" | "bus" | "objectStorage">, host: SiteRuntimeHost, viewer: SiteRuntimeViewer, sessionId: string, retainedToolIds: readonly string[]): Promise<void>;
|
|
17
|
+
export declare function normalizeSiteAgentSession(deps: Pick<ApiRouteDeps, "db" | "settings" | "bus" | "objectStorage">, host: SiteRuntimeHost, viewer: SiteRuntimeViewer, sessionId: string, retainedToolIds: readonly string[], memberPermissions?: readonly Permission[]): Promise<void>;
|
|
@@ -7,8 +7,8 @@ import { type SiteRuntimeViewer } from "./viewer.js";
|
|
|
7
7
|
/**
|
|
8
8
|
* Site-runtime: the narrow API a Site gateway calls for one published Site
|
|
9
9
|
* host, after the gateway signature passed. Everything here works for exactly
|
|
10
|
-
* one Site: the host names it
|
|
11
|
-
*
|
|
10
|
+
* one Site: the host names it and the viewer is resolved for it. Members retain
|
|
11
|
+
* their human identity; outsiders use the viewer-bound `site:<siteId>` principal.
|
|
12
12
|
*/
|
|
13
13
|
export type SiteRuntimeDeps = Pick<ApiRouteDeps, "db" | "settings" | "objectStorage">;
|
|
14
14
|
export type SiteRuntimeHost = {
|
|
@@ -43,7 +43,7 @@ export declare function resolveSiteRuntimeViewer(deps: SiteRuntimeDeps, host: Si
|
|
|
43
43
|
ignoreViewAs?: boolean;
|
|
44
44
|
}): Promise<SiteRuntimeViewer>;
|
|
45
45
|
/** What a viewer cannot do on a Site: 403 with `details.code` `not_available_for_viewer`. */
|
|
46
|
-
export type SiteViewerFeature = "agent" | "agent_model" | "workspace_tools" | "site_data" | "site_sql";
|
|
46
|
+
export type SiteViewerFeature = "agent" | "private_conversations" | "agent_model" | "workspace_tools" | "site_data" | "site_sql";
|
|
47
47
|
export declare function siteNotAvailableForViewer(feature: SiteViewerFeature, message: string, extra?: Record<string, unknown>): import("../http/api-error.js").ApiHttpError;
|
|
48
48
|
/** The viewer capabilities a page reads from `site.config` to hide what won't work. */
|
|
49
49
|
export declare function siteViewerCapabilities(deps: SiteRuntimeDeps, host: SiteRuntimeHost, viewer: SiteRuntimeViewer, limits: SiteLimits): SiteViewerCapabilities;
|
|
@@ -160,9 +160,9 @@ export declare function meterSiteRuntime(deps: SiteRuntimeDeps, host: SiteRuntim
|
|
|
160
160
|
viewer?: SiteRuntimeViewer;
|
|
161
161
|
}): Promise<void>;
|
|
162
162
|
/**
|
|
163
|
-
* Run an ordinary session route in this process as the
|
|
164
|
-
* principal
|
|
165
|
-
*
|
|
163
|
+
* Run an ordinary session route in this process as the verified member, or
|
|
164
|
+
* the Site service principal for an outsider. The latter uses a 60-second
|
|
165
|
+
* delegated token plus the separate trusted viewer audience. The Site and the
|
|
166
166
|
* served version ride along as the request's Site origin, so new sessions and
|
|
167
167
|
* turns record where they came from. The route authorizes as usual.
|
|
168
168
|
*/
|
|
@@ -174,6 +174,7 @@ export declare function siteRuntimeSessionRequest(deps: SiteRuntimeDeps, host: S
|
|
|
174
174
|
path: string;
|
|
175
175
|
body?: unknown;
|
|
176
176
|
extraPermissions?: readonly Permission[];
|
|
177
|
+
reauthorize: () => Promise<void>;
|
|
177
178
|
}): Promise<Request>;
|
|
178
179
|
/** A forwarded response without cookies (site-runtime never sets any). */
|
|
179
180
|
export declare function siteRuntimeForwardedResponse(response: Response): Response;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ErrorCode, type Permission, type SiteHostMapRecord, type SiteHostnameAvailability, type SitePublication, type UpsertSitePublicationRequest } from "@opengeni/contracts";
|
|
2
2
|
import type { AccessGrant } from "@opengeni/contracts";
|
|
3
|
-
import { type ApiRouteDeps } from "@opengeni/core";
|
|
3
|
+
import { type ApiRouteDeps, type cancelPublishedSiteSession } from "@opengeni/core";
|
|
4
4
|
import { SitePublishingError, type PublishedSiteRecord, type SiteDataSite, type SiteHostnameEdgeState, type SitePublicationRecord, type SitePublishingScope, type StoredSiteLimitProfile } from "@opengeni/db";
|
|
5
5
|
import { ApiHttpError } from "../http/api-error.js";
|
|
6
6
|
import { type SiteContentScanner } from "./content-scanner.js";
|
|
@@ -40,13 +40,12 @@ export type SitePublishingEffects = {
|
|
|
40
40
|
};
|
|
41
41
|
/** Background effects running at once per process. */
|
|
42
42
|
export declare const SITE_EFFECT_CONCURRENCY = 4;
|
|
43
|
+
type SiteCancellation = (input: Parameters<typeof cancelPublishedSiteSession>[1]) => Promise<unknown>;
|
|
43
44
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* session routes. Without it (the operator script) cancellation is skipped.
|
|
45
|
+
* Internal host lifecycle adapter. This never grants the Site service bearer
|
|
46
|
+
* ordinary session access and never impersonates a conversation's viewer.
|
|
47
47
|
*/
|
|
48
|
-
export declare function
|
|
49
|
-
export declare function sitePublishingDispatch(settings: ApiRouteDeps["settings"]): ((request: Request) => Promise<Response>) | null;
|
|
48
|
+
export declare function setSitePublishingCancellation(settings: ApiRouteDeps["settings"], cancel: SiteCancellation): void;
|
|
50
49
|
type SitePublishingEffectsOptions = {
|
|
51
50
|
fetch?: typeof fetch;
|
|
52
51
|
backoffMs?: number;
|
|
@@ -164,11 +163,9 @@ export declare function siteServiceSessionRequest(deps: Pick<SitePublishingDeps,
|
|
|
164
163
|
* Stop a Site's viewers after a takedown, unpublish or narrower access:
|
|
165
164
|
* revoke every site-session now, and after commit cancel the Site's agent
|
|
166
165
|
* conversations that may still be working (in-flight turns and queued
|
|
167
|
-
* messages),
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* script) the cancellation is skipped and logged; per-conversation caps
|
|
171
|
-
* still bound what running turns can spend.
|
|
166
|
+
* messages), through the native cancel-only lifecycle adapter. The original
|
|
167
|
+
* actor survives queued background execution. No transcript access is granted.
|
|
168
|
+
* Every page is drained, including old conversations; there is no age cutoff.
|
|
172
169
|
*/
|
|
173
170
|
export declare function stopSiteViewers(deps: SitePublishingDeps, scope: SitePublishingScope, siteId: string, options: {
|
|
174
171
|
revokeSessions?: boolean;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function bindSiteSessionRequestAuthority(request: Request, reauthorize: () => Promise<void>): void;
|
|
2
|
+
/** Periodic SSE reauthorization must check the Site cookie, not only its delegate. */
|
|
3
|
+
export declare function reauthorizeSiteSessionRequest(request: Request): Promise<void>;
|
|
@@ -23,5 +23,5 @@ export declare function workspaceGatewaySecretArguments(input: {
|
|
|
23
23
|
settings: Settings;
|
|
24
24
|
grant: AccessGrant;
|
|
25
25
|
/** Connected vaults (1Password, host) for the resolved acting subject. */
|
|
26
|
-
providersForSubject?: ((subjectId: string | null) => VaultProviderRegistry) | undefined;
|
|
26
|
+
providersForSubject?: ((subjectId: string | null, site?: SiteSecretContext | null) => VaultProviderRegistry) | undefined;
|
|
27
27
|
}): ToolGatewayArgumentResolver;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Opengeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -42,29 +42,32 @@
|
|
|
42
42
|
"@aws-sdk/client-s3": "^3.1044.0",
|
|
43
43
|
"@aws-sdk/s3-request-presigner": "^3.1044.0",
|
|
44
44
|
"@hono/zod-validator": "^0.7.6",
|
|
45
|
+
"@keeper-security/secrets-manager-core": "17.6.0",
|
|
45
46
|
"@llamaindex/liteparse": "2.14.2",
|
|
46
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
47
|
-
"@opengeni/agent-proto": "3.
|
|
48
|
-
"@opengeni/artifact-tool": "3.
|
|
49
|
-
"@opengeni/capabilities": "3.
|
|
50
|
-
"@opengeni/codemode": "3.
|
|
51
|
-
"@opengeni/codex": "3.
|
|
52
|
-
"@opengeni/config": "3.
|
|
53
|
-
"@opengeni/contracts": "3.
|
|
54
|
-
"@opengeni/core": "3.
|
|
55
|
-
"@opengeni/db": "3.
|
|
56
|
-
"@opengeni/documents": "3.
|
|
57
|
-
"@opengeni/events": "3.
|
|
58
|
-
"@opengeni/github": "3.
|
|
59
|
-
"@opengeni/interaction": "3.
|
|
60
|
-
"@opengeni/network": "3.
|
|
61
|
-
"@opengeni/observability": "3.
|
|
62
|
-
"@opengeni/runtime": "3.
|
|
63
|
-
"@opengeni/storage": "3.
|
|
64
|
-
"@opengeni/tool-gateway": "3.
|
|
65
|
-
"@opengeni/vaults": "3.
|
|
66
|
-
"@opengeni/vaults-
|
|
67
|
-
"@opengeni/
|
|
48
|
+
"@opengeni/agent-proto": "3.3.0",
|
|
49
|
+
"@opengeni/artifact-tool": "3.3.0",
|
|
50
|
+
"@opengeni/capabilities": "3.3.0",
|
|
51
|
+
"@opengeni/codemode": "3.3.0",
|
|
52
|
+
"@opengeni/codex": "3.3.0",
|
|
53
|
+
"@opengeni/config": "3.3.0",
|
|
54
|
+
"@opengeni/contracts": "3.3.0",
|
|
55
|
+
"@opengeni/core": "3.3.0",
|
|
56
|
+
"@opengeni/db": "3.3.0",
|
|
57
|
+
"@opengeni/documents": "3.3.0",
|
|
58
|
+
"@opengeni/events": "3.3.0",
|
|
59
|
+
"@opengeni/github": "3.3.0",
|
|
60
|
+
"@opengeni/interaction": "3.3.0",
|
|
61
|
+
"@opengeni/network": "3.3.0",
|
|
62
|
+
"@opengeni/observability": "3.3.0",
|
|
63
|
+
"@opengeni/runtime": "3.3.0",
|
|
64
|
+
"@opengeni/storage": "3.3.0",
|
|
65
|
+
"@opengeni/tool-gateway": "3.3.0",
|
|
66
|
+
"@opengeni/vaults": "3.3.0",
|
|
67
|
+
"@opengeni/vaults-keeper": "3.3.0",
|
|
68
|
+
"@opengeni/vaults-onepassword": "3.3.0",
|
|
69
|
+
"@opengeni/vaults-protonpass": "3.3.0",
|
|
70
|
+
"@opengeni/xai-subscription": "3.3.0",
|
|
68
71
|
"@temporalio/client": "^1.17.0",
|
|
69
72
|
"ajv": "^8.20.0",
|
|
70
73
|
"better-auth": "^1.6.14",
|
package/src/access-grant-rls.ts
CHANGED
|
@@ -87,6 +87,7 @@ export async function withAccessGrantSessionRlsContext<T>(
|
|
|
87
87
|
{
|
|
88
88
|
subjectId: actor.subjectId,
|
|
89
89
|
initiatingHumanSubjectId: actor.initiatingHumanSubjectId,
|
|
90
|
+
agentRootSessionId: actor.callerRootSessionId,
|
|
90
91
|
privateFileOwnerSubjectId:
|
|
91
92
|
learning.defaultScope === "personal" ? learning.subjectId : null,
|
|
92
93
|
},
|
package/src/app.ts
CHANGED
|
@@ -2200,8 +2200,20 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
2200
2200
|
const interactionSecrets = createVaultInteractionSecretsPort({
|
|
2201
2201
|
db: routeDeps.db,
|
|
2202
2202
|
encryptionKey: environmentsEncryptionKeyBytes(routeDeps.settings) ?? null,
|
|
2203
|
-
providersFor: (workspaceId, subjectId) =>
|
|
2204
|
-
vaultProviderRegistryFor(routeDeps, {
|
|
2203
|
+
providersFor: (workspaceId, subjectId, principal) =>
|
|
2204
|
+
vaultProviderRegistryFor(routeDeps, {
|
|
2205
|
+
workspaceId,
|
|
2206
|
+
subjectId,
|
|
2207
|
+
...(principal
|
|
2208
|
+
? {
|
|
2209
|
+
executionContext: {
|
|
2210
|
+
sessionId: principal.sessionId,
|
|
2211
|
+
turnId: principal.turnId,
|
|
2212
|
+
attemptId: principal.attemptId,
|
|
2213
|
+
},
|
|
2214
|
+
}
|
|
2215
|
+
: {}),
|
|
2216
|
+
}),
|
|
2205
2217
|
});
|
|
2206
2218
|
registerBrowserSessionRoutes(app, routeDeps, { interactionSecrets });
|
|
2207
2219
|
registerComputerSessionRoutes(app, routeDeps, { interactionSecrets });
|
package/src/http/sse.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getWorkspaceInteractionRevisionState,
|
|
8
8
|
listSessionEventPage,
|
|
9
9
|
listWorkspaceControlEvents,
|
|
10
|
+
getWorkspaceControlEvent,
|
|
10
11
|
readSessionEventStorageGapEnd,
|
|
11
12
|
type Database,
|
|
12
13
|
} from "@opengeni/db";
|
|
@@ -677,7 +678,10 @@ export async function sseWorkspaceControlStream(
|
|
|
677
678
|
}
|
|
678
679
|
}
|
|
679
680
|
}
|
|
680
|
-
|
|
681
|
+
// The bus is workspace-wide, not an authorization projection. Re-read the
|
|
682
|
+
// exact durable row under this stream's actor before delivering live data.
|
|
683
|
+
const visible = await getWorkspaceControlEvent(db, workspaceId, event.id);
|
|
684
|
+
if (visible) await writeFrame(formatWorkspaceControlEventSse(visible));
|
|
681
685
|
lastSent = event.sequence;
|
|
682
686
|
};
|
|
683
687
|
delivery = createLatestWinsDelivery(send, fail);
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ import { createAutomaticAgentUpdater } from "./sandbox/agent-update";
|
|
|
52
52
|
import { startSlackInteractionPump } from "./integrations/slack-interactions";
|
|
53
53
|
import { startMemorySlackPublicationPump } from "./memory-slack-delivery";
|
|
54
54
|
import { startTemporalScheduleCleanupPump } from "./temporal-schedule-cleanup";
|
|
55
|
+
import { closeNativeExternalVaultProviders } from "./integrations/external-vault-providers";
|
|
55
56
|
import { startScheduledTaskLifecyclePump } from "./scheduled-task-lifecycle";
|
|
56
57
|
import { createTemporalScheduleSynchronizer } from "./temporal-schedule-sync";
|
|
57
58
|
import { startNativePushDispatchPump } from "./native-push-dispatch";
|
|
@@ -73,6 +74,7 @@ import {
|
|
|
73
74
|
type StandaloneEditableArtifactApplication,
|
|
74
75
|
} from "./editable-artifact-production";
|
|
75
76
|
import { installApiFatalProcessBoundary } from "./fatal-process-boundary";
|
|
77
|
+
import { installApiShutdownHandlers } from "./shutdown";
|
|
76
78
|
|
|
77
79
|
/**
|
|
78
80
|
* A REJECT_DUPLICATE start collides on the deterministic workflowId when the
|
|
@@ -590,6 +592,8 @@ export async function startApi(
|
|
|
590
592
|
server,
|
|
591
593
|
close: async () => {
|
|
592
594
|
server.stop(true);
|
|
595
|
+
// Start private CLI session cleanup even if another service's drain stalls.
|
|
596
|
+
const vaultProvidersClosed = closeNativeExternalVaultProviders().catch(() => undefined);
|
|
593
597
|
metricsServer?.stop(true);
|
|
594
598
|
stopSlackInteractionPump?.();
|
|
595
599
|
await stopMemorySlackPublicationPump();
|
|
@@ -602,6 +606,7 @@ export async function startApi(
|
|
|
602
606
|
// Write queued presence before the database pool closes.
|
|
603
607
|
await routeDeps.userPresence?.close().catch(() => undefined);
|
|
604
608
|
await Promise.allSettled([
|
|
609
|
+
vaultProvidersClosed,
|
|
605
610
|
Promise.resolve(editableArtifactComposition?.close()),
|
|
606
611
|
authCalloutResponder?.close(),
|
|
607
612
|
bus.close(),
|
|
@@ -618,7 +623,14 @@ if (import.meta.main) {
|
|
|
618
623
|
const settings = getSettings();
|
|
619
624
|
const observability = createObservability(settings, { component: "api" });
|
|
620
625
|
fatalBoundary.attachObservability(observability);
|
|
621
|
-
await startApi({ settings, observability });
|
|
626
|
+
const api = await startApi({ settings, observability });
|
|
627
|
+
installApiShutdownHandlers({
|
|
628
|
+
close: api.close,
|
|
629
|
+
onFailure: () =>
|
|
630
|
+
observability.warn("Opengeni API shutdown did not finish cleanly", {
|
|
631
|
+
errorCode: "api_shutdown_failed",
|
|
632
|
+
}),
|
|
633
|
+
});
|
|
622
634
|
fatalBoundary.markRunning();
|
|
623
635
|
} catch (error) {
|
|
624
636
|
await fatalBoundary.reportStartupFailure(error);
|
|
@@ -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/worker/src/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
|
+
}
|
|
@@ -59,7 +59,11 @@ export function createVaultInteractionSecretsPort(deps: {
|
|
|
59
59
|
/** Defaults to a service over the same database and key. */
|
|
60
60
|
vaultService?: VaultService;
|
|
61
61
|
/** Connected vaults (1Password, host) for a workspace's acting subject. */
|
|
62
|
-
providersFor?: (
|
|
62
|
+
providersFor?: (
|
|
63
|
+
workspaceId: string,
|
|
64
|
+
subjectId: string | null,
|
|
65
|
+
principal?: AttemptSecretPrincipal,
|
|
66
|
+
) => VaultProviderRegistry;
|
|
63
67
|
}): InteractionSecretsPort {
|
|
64
68
|
const encryptionKey = deps.encryptionKey;
|
|
65
69
|
let service = deps.vaultService ?? null;
|
|
@@ -203,7 +207,7 @@ export function createVaultInteractionSecretsPort(deps: {
|
|
|
203
207
|
...(providersFor
|
|
204
208
|
? {
|
|
205
209
|
providersForSubject: (subjectId: string | null) =>
|
|
206
|
-
providersFor(principal.workspaceId, subjectId),
|
|
210
|
+
providersFor(principal.workspaceId, subjectId, principal),
|
|
207
211
|
}
|
|
208
212
|
: {}),
|
|
209
213
|
});
|