@opengeni/api-router 2.5.0 → 2.6.4
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/auth/managed-auth-attempt-context.d.ts +5 -1
- package/dist/auth/managed-auth.d.ts +24 -1
- package/dist/{chunk-QESX7HDK.js → chunk-XTLI3CBH.js} +2236 -387
- package/dist/chunk-XTLI3CBH.js.map +1 -0
- package/dist/http/sse.d.ts +9 -0
- package/dist/index.js +104 -4
- package/dist/index.js.map +1 -1
- package/dist/interaction-metrics.d.ts +2 -0
- package/dist/mcp/company-brain-governed-writes.d.ts +1 -1
- package/dist/mcp/company-profile-agent-admin.d.ts +6 -6
- package/dist/mcp/remember.d.ts +2 -2
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/session-view.d.ts +1 -0
- package/dist/mcp/session-wait.d.ts +19 -0
- package/dist/routes/api-keys.d.ts +2 -0
- package/dist/routes/browser-sessions.d.ts +1 -0
- package/dist/routes/computer-sessions.d.ts +12 -0
- package/dist/routes/managed-auth-session-sets.d.ts +7 -0
- package/dist/routes/workspaces.d.ts +1 -1
- package/dist/sandbox/metrics-ingestion.d.ts +16 -0
- package/dist/workspace-delete-observability.d.ts +10 -0
- package/package.json +15 -15
- package/src/app.ts +172 -20
- package/src/auth/managed-auth-attempt-context.ts +40 -3
- package/src/auth/managed-auth-session-adapter.ts +1 -0
- package/src/auth/managed-auth.ts +164 -4
- package/src/http/sse.ts +279 -45
- package/src/integrations/oauth-client.ts +8 -1
- package/src/integrations/provider-oauth.ts +12 -2
- package/src/interaction-metrics.ts +30 -0
- package/src/mcp/company-brain-governed-writes.ts +38 -23
- package/src/mcp/company-profile-agent-admin.ts +7 -7
- package/src/mcp/remember.ts +19 -8
- package/src/mcp/server.ts +318 -26
- package/src/mcp/session-wait.ts +56 -8
- package/src/routes/api-integrations.ts +2 -2
- package/src/routes/api-keys.ts +149 -7
- package/src/routes/browser-sessions.ts +10 -2
- package/src/routes/capabilities.ts +3 -3
- package/src/routes/codex.ts +483 -74
- package/src/routes/company-profile.ts +64 -0
- package/src/routes/computer-sessions.ts +103 -1
- package/src/routes/integration-facets.ts +8 -5
- package/src/routes/interaction-resources.ts +7 -1
- package/src/routes/managed-auth-session-sets.ts +199 -2
- package/src/routes/organization-memberships.ts +28 -8
- package/src/routes/packs.ts +5 -5
- package/src/routes/plugins.ts +2 -2
- package/src/routes/scheduled-tasks.ts +9 -0
- package/src/routes/sessions.ts +3 -6
- package/src/routes/skills.ts +3 -3
- package/src/routes/workspaces.ts +293 -54
- package/src/sandbox/channel-a.ts +10 -4
- package/src/sandbox/machines.ts +13 -6
- package/src/sandbox/metrics-ingestion.ts +157 -3
- package/src/sandbox/viewer.ts +20 -1
- package/src/workspace-delete-observability.ts +75 -0
- package/dist/chunk-QESX7HDK.js.map +0 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { AuthRunMutationResponse, BrowserActionReceipt, BrowserActionRequest, PublishBrowserRevisionResponse, BrowserSessionMutationResponse, ComputerActionReceipt, ComputerActionRequest, ComputerSessionMutationResponse, InteractionInterventionMutationResponse, ProtectedAuthFillResponse } from "@opengeni/contracts";
|
|
2
2
|
import { type Observability } from "@opengeni/observability";
|
|
3
|
+
import type { ComputerFrameEvidenceMismatchReason } from "@opengeni/runtime/sandbox";
|
|
3
4
|
type InteractionLifecycleMutation = BrowserSessionMutationResponse | ComputerSessionMutationResponse;
|
|
4
5
|
type AuthMutation = AuthRunMutationResponse | ProtectedAuthFillResponse;
|
|
5
6
|
export declare function observeBrowserActionResult(observability: Observability | null | undefined, startedAtMs: number, request: BrowserActionRequest, receipt: BrowserActionReceipt): void;
|
|
6
7
|
export declare function observeComputerActionResult(observability: Observability | null | undefined, startedAtMs: number, request: ComputerActionRequest, receipt: ComputerActionReceipt): void;
|
|
8
|
+
export declare function observeComputerFrameEvidenceMismatch(observability: Observability | null | undefined, reason: ComputerFrameEvidenceMismatchReason): void;
|
|
7
9
|
export declare function observeLifecycleResult(observability: Observability | null | undefined, startedAtMs: number, response: InteractionLifecycleMutation): void;
|
|
8
10
|
export declare function observeBrowserRevisionPublication(observability: Observability | null | undefined, startedAtMs: number, response: PublishBrowserRevisionResponse): void;
|
|
9
11
|
export declare function observeAuthMutation(observability: Observability | null | undefined, startedAtMs: number, response: AuthMutation): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CompanyBrainGovernedWriteAttempt } from "@opengeni/contracts";
|
|
2
2
|
import { createCompanyBrainLearningPolicyRouter } from "@opengeni/core";
|
|
3
|
-
import type
|
|
3
|
+
import { type Database } from "@opengeni/db";
|
|
4
4
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
type JsonResult = (value: unknown) => {
|
|
6
6
|
content: {
|
|
@@ -33,12 +33,12 @@ export declare function deriveCompanyProfileStableKey(content: string): string;
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function resolveCompanyProfileEntries(input: readonly EntryInput[]): CompanyProfileEntry[];
|
|
35
35
|
/**
|
|
36
|
-
* Register the
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
36
|
+
* Register the organization profile administration path. This is not derived
|
|
37
|
+
* workspace learning: the initiating human must be the organization's active
|
|
38
|
+
* owner (the same authority as the manual `account:admin` route). The separate
|
|
39
|
+
* organization policy either blocks the proposal, keeps the exact bound
|
|
40
|
+
* human-confirmation path, or permits immediate activation through the native
|
|
41
|
+
* company-profile lifecycle.
|
|
42
42
|
*/
|
|
43
43
|
export declare function registerCompanyProfileAgentAdminTools(input: RegisterCompanyProfileAgentAdminToolsInput): void;
|
|
44
44
|
export {};
|
package/dist/mcp/remember.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CompanyBrainGovernedWriteAttempt } from "@opengeni/contracts";
|
|
2
2
|
import { createRememberRouter } from "@opengeni/core";
|
|
3
|
-
import type
|
|
3
|
+
import { type Database } from "@opengeni/db";
|
|
4
4
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
type JsonResult = (value: unknown) => {
|
|
6
6
|
content: {
|
|
@@ -19,7 +19,7 @@ export type RegisterRememberToolsInput = {
|
|
|
19
19
|
/**
|
|
20
20
|
* Explicit user-directed remember. Content becomes exact task-note evidence,
|
|
21
21
|
* promotion runs through the frozen workspace learning policy, and activation
|
|
22
|
-
* is either automatic (
|
|
22
|
+
* is either automatic (Skill or instruction under Automatic mode) or requires exactly one
|
|
23
23
|
* bound human confirmation through the built-in `request_human_input` tool.
|
|
24
24
|
* Tool input cannot select scope beyond the workspace, active authority, or
|
|
25
25
|
* another learning-policy source.
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -90,6 +90,7 @@ export declare function capSessionDiscoveryPage(page: Awaited<ReturnType<typeof
|
|
|
90
90
|
pausedDescendants: number;
|
|
91
91
|
failedDescendants: number;
|
|
92
92
|
unreadDescendants?: number | undefined;
|
|
93
|
+
unreadFailedDescendants?: number | undefined;
|
|
93
94
|
activelyWorkingDescendants?: number | undefined;
|
|
94
95
|
attentionSince?: string | null | undefined;
|
|
95
96
|
truncated: boolean;
|
|
@@ -121,6 +121,7 @@ export declare function boundSessionDetailMcp(session: Session, effectiveControl
|
|
|
121
121
|
pausedDescendants: number;
|
|
122
122
|
failedDescendants: number;
|
|
123
123
|
unreadDescendants?: number | undefined;
|
|
124
|
+
unreadFailedDescendants?: number | undefined;
|
|
124
125
|
activelyWorkingDescendants?: number | undefined;
|
|
125
126
|
attentionSince?: string | null | undefined;
|
|
126
127
|
truncated: boolean;
|
|
@@ -40,6 +40,21 @@ export declare const SESSION_WAIT_EVENTS_PER_TARGET = 20;
|
|
|
40
40
|
* noise never wake a waiter; `session_events` remains the drill-down for them.
|
|
41
41
|
*/
|
|
42
42
|
export declare const SESSION_WAIT_EVENT_TYPES: readonly ["turn.started", "turn.completed", "turn.failed", "turn.cancelled", "turn.superseded", "turn.capacity_waiting", "agent.message.completed", "session.status.changed", "session.requiresAction", "session.humanInput.requested", "session.control.paused", "session.control.resumed", "tool.auth_needed", "credential.auth_needed", "rig.setup.failed", "goal.set", "goal.updated", "goal.progress", "goal.rewrite.proposed", "goal.rewrite.rejected", "goal.completed", "goal.paused", "goal.resumed", "goal.cleared", "goal.continuation"];
|
|
43
|
+
/**
|
|
44
|
+
* Settlement and blocking events that make a child result usable.
|
|
45
|
+
* Goal facts are deliberately absent: an agent can complete its durable goal
|
|
46
|
+
* before it emits the final assistant message and settles the turn. Completed
|
|
47
|
+
* agent messages are also absent because commentary messages use the same
|
|
48
|
+
* event type; an ordinary result-bearing `turn.completed` carries the
|
|
49
|
+
* authoritative final output.
|
|
50
|
+
*/
|
|
51
|
+
export declare const SESSION_WAIT_COMPLETION_EVENT_TYPES: readonly ["turn.completed", "turn.failed", "turn.cancelled", "turn.superseded", "turn.capacity_waiting", "session.requiresAction", "session.humanInput.requested", "session.control.paused", "tool.auth_needed", "credential.auth_needed", "rig.setup.failed", "goal.paused"];
|
|
52
|
+
/**
|
|
53
|
+
* A completed turn is result-bearing only when it carries the ordinary final
|
|
54
|
+
* output. Segment-limit and maintenance turns settle one execution segment
|
|
55
|
+
* while the session still has work to do, so they must not release a parent.
|
|
56
|
+
*/
|
|
57
|
+
export declare function sessionWaitCompletionEventMatches(event: SessionEvent): boolean;
|
|
43
58
|
/** The self-session event that announces a newly pending machine input. */
|
|
44
59
|
export declare const SESSION_WAIT_OWN_PENDING_EVENT_TYPE = "system.update.pending";
|
|
45
60
|
/**
|
|
@@ -120,6 +135,10 @@ export type SessionWaitInput = {
|
|
|
120
135
|
targets: readonly SessionWaitTarget[];
|
|
121
136
|
ownSessionId: string | null;
|
|
122
137
|
maxWaitMs: number;
|
|
138
|
+
/** Target events that end the wait. Defaults to the ordinary activity set. */
|
|
139
|
+
targetEventTypes?: readonly SessionEventType[] | undefined;
|
|
140
|
+
/** Optional payload-aware refinement applied after the event-type filter. */
|
|
141
|
+
targetEventMatches?: ((event: SessionEvent) => boolean) | undefined;
|
|
123
142
|
source: SessionWaitSource;
|
|
124
143
|
signal?: AbortSignal | undefined;
|
|
125
144
|
now?: (() => number) | undefined;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Permission } from "@opengeni/contracts";
|
|
1
2
|
import type { Hono } from "hono";
|
|
2
3
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
4
|
+
export declare const organizationApiKeyPermissions: Permission[];
|
|
3
5
|
export declare function registerApiKeyRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
@@ -5,6 +5,7 @@ export declare function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDe
|
|
|
5
5
|
export declare function browserNeedsStandaloneDisplayStack(input: {
|
|
6
6
|
headless: boolean;
|
|
7
7
|
linkedComputer: boolean;
|
|
8
|
+
nativeBrowserControl: boolean;
|
|
8
9
|
}): boolean;
|
|
9
10
|
export declare function interactionActorForGrant(grant: AccessGrant): ReturnType<typeof InteractionActor.parse>;
|
|
10
11
|
/** Resolve Drive authority from the same immutable session authorization that
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { type ApiRouteDeps } from "@opengeni/core";
|
|
2
|
+
import { type ComputerControlFrame, type ExpectedComputerFrameEvidence } from "@opengeni/runtime/sandbox";
|
|
2
3
|
import type { Hono } from "hono";
|
|
4
|
+
type ComputerFrameCaptureClient = {
|
|
5
|
+
capture(targetId: string, options: {
|
|
6
|
+
format?: "jpeg" | "png";
|
|
7
|
+
quality?: number;
|
|
8
|
+
maxWidth?: number;
|
|
9
|
+
maxHeight?: number;
|
|
10
|
+
}): Promise<ComputerControlFrame>;
|
|
11
|
+
};
|
|
12
|
+
export declare function validateComputerFrameForApi(frame: Pick<ComputerControlFrame, "data" | "mediaType" | "metadataHeader">, expected: ExpectedComputerFrameEvidence): ComputerControlFrame;
|
|
13
|
+
export declare function captureModelComputerFrame(sessionClient: ComputerFrameCaptureClient, expected: ExpectedComputerFrameEvidence): Promise<ComputerControlFrame>;
|
|
3
14
|
/** Public ComputerSession resource surface. Physical app/window authority stays
|
|
4
15
|
* in the same placement controller used by BrowserSession; this route owns only
|
|
5
16
|
* durable authorization, placement fencing, lifecycle receipts, and routing. */
|
|
6
17
|
export declare function registerComputerSessionRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
18
|
+
export {};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { type ApiRouteDeps } from "@opengeni/core";
|
|
2
2
|
import type { Hono } from "hono";
|
|
3
3
|
export declare function registerManagedAuthSessionSetRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
4
|
+
export declare function validatedManagedAuthSocialAuthorizationUrl(input: {
|
|
5
|
+
provider: "google" | "github";
|
|
6
|
+
rawUrl: unknown;
|
|
7
|
+
expectedClientId: string;
|
|
8
|
+
expectedCallbackUrl: string;
|
|
9
|
+
}): string;
|
|
4
10
|
/**
|
|
5
11
|
* Better Auth 1.6.26 provider lifecycle routes retained while session-set mode
|
|
6
12
|
* owns all selected-session reads, enumeration, revocation, and user mutation.
|
|
@@ -11,6 +17,7 @@ export declare function requireManagedAuthProviderRouteAllowed(method: string, p
|
|
|
11
17
|
/** Remove provider bearer/session material and enforce browser-auth cache/cookie policy. */
|
|
12
18
|
export declare function scrubManagedAuthProviderResponse(response: Response, options?: {
|
|
13
19
|
replacementCookies?: readonly string[] | undefined;
|
|
20
|
+
preserveCookieNames?: readonly string[] | undefined;
|
|
14
21
|
}): Promise<Response>;
|
|
15
22
|
export declare function parseSupportedDeepLink(path: string): {
|
|
16
23
|
workspaceId: string | null;
|
|
@@ -16,7 +16,7 @@ export declare function workspaceMembersResponse(members: readonly WorkspaceMemb
|
|
|
16
16
|
subjectId: string;
|
|
17
17
|
subjectLabel: string | null;
|
|
18
18
|
role: string;
|
|
19
|
-
permissions: ("account:admin" | "account:read" | "api_keys:manage" | "artifacts:publish" | "artifacts:read" | "billing:manage" | "billing:read" | "codemode:call" | "connections:read" | "connections:write" | "documents:manage" | "documents:search" | "enrollments:manage" | "enrollments:read" | "environments:manage" | "environments:use" | "files:read" | "files:upload" | "files:write" | "github:manage" | "github:use" | "goals:manage" | "mcp_servers:attach" | "members:manage" | "rigs:manage" | "rigs:use" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "secrets:list" | "secrets:read" | "secrets:write" | "sessions:control" | "sessions:create" | "sessions:read" | "stream:acknowledge" | "stream:control" | "stream:view" | "terminal:attach" | "variable-sets:attach" | "variable-sets:list" | "variable-sets:manage" | "variable-sets:read" | "variable-sets:use" | "variable-sets:write" | "workspace:admin" | "workspace:create" | "workspace:read")[];
|
|
19
|
+
permissions: ("account:admin" | "account:read" | "api_keys:manage" | "artifacts:publish" | "artifacts:read" | "billing:manage" | "billing:read" | "capabilities:manage" | "codemode:call" | "connections:read" | "connections:write" | "documents:manage" | "documents:search" | "enrollments:manage" | "enrollments:read" | "environments:manage" | "environments:use" | "files:read" | "files:upload" | "files:write" | "github:manage" | "github:use" | "goals:manage" | "mcp_servers:attach" | "members:manage" | "rigs:manage" | "rigs:use" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "secrets:list" | "secrets:read" | "secrets:write" | "sessions:control" | "sessions:create" | "sessions:read" | "stream:acknowledge" | "stream:control" | "stream:view" | "terminal:attach" | "variable-sets:attach" | "variable-sets:list" | "variable-sets:manage" | "variable-sets:read" | "variable-sets:use" | "variable-sets:write" | "workspace:admin" | "workspace:create" | "workspace:read")[];
|
|
20
20
|
createdAt: string;
|
|
21
21
|
}[];
|
|
22
22
|
};
|
|
@@ -7,6 +7,22 @@ import { Hello, type AttachedBrowserInventorySnapshot as WireAttachedBrowserInve
|
|
|
7
7
|
export declare const AGENT_EVENTS_SUBJECT = "agent.*.*.connection.*.events";
|
|
8
8
|
/** The wildcard subject the agent publishes its connect Hello on. */
|
|
9
9
|
export declare const AGENT_HELLO_SUBJECT = "agent.*.*.connection.*.hello";
|
|
10
|
+
export type AgentEventIngestionScheduler = {
|
|
11
|
+
enqueue: (payload: Uint8Array, subject: string) => void;
|
|
12
|
+
whenIdle: () => Promise<void>;
|
|
13
|
+
close: () => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Drain agent events without making every Connected Machine wait behind one
|
|
17
|
+
* deployment-wide serial DB queue. Each exact process subject retains FIFO order;
|
|
18
|
+
* separate subjects drain concurrently. Consecutive pending heartbeats for one
|
|
19
|
+
* subject are latest-wins because they are point-in-time liveness/metrics samples,
|
|
20
|
+
* while GoingOffline and update-progress events remain ordered barriers.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createAgentEventIngestionScheduler(handler: (payload: Uint8Array, subject: string) => void | Promise<void>, options?: {
|
|
23
|
+
maxConcurrentSubjects?: number;
|
|
24
|
+
onHeartbeatCoalesced?: () => void;
|
|
25
|
+
}): AgentEventIngestionScheduler;
|
|
10
26
|
/** Parse the exact process events subject (heartbeat plane). */
|
|
11
27
|
export declare function parseAgentEventSubject(subject: string): {
|
|
12
28
|
workspaceId: string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorkspaceDeleteObserver } from "@opengeni/db";
|
|
2
|
+
import type { Observability } from "@opengeni/observability";
|
|
3
|
+
/**
|
|
4
|
+
* Retain exact workspace deletion timing and inventory without putting tenant
|
|
5
|
+
* identifiers in metric labels. Observer failures never change deletion truth.
|
|
6
|
+
*/
|
|
7
|
+
export declare function workspaceDeleteObserver(observability: Observability | null | undefined, identity: {
|
|
8
|
+
accountId: string;
|
|
9
|
+
workspaceId: string;
|
|
10
|
+
}): WorkspaceDeleteObserver | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.4",
|
|
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": {
|
|
@@ -41,24 +41,24 @@
|
|
|
41
41
|
"@aws-sdk/client-s3": "^3.1044.0",
|
|
42
42
|
"@aws-sdk/s3-request-presigner": "^3.1044.0",
|
|
43
43
|
"@hono/zod-validator": "^0.7.6",
|
|
44
|
-
"@llamaindex/liteparse": "
|
|
44
|
+
"@llamaindex/liteparse": "2.14.2",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
46
|
"@opengeni/agent-proto": "^0.5.1",
|
|
47
|
-
"@opengeni/artifact-tool": "^0.3.
|
|
47
|
+
"@opengeni/artifact-tool": "^0.3.14",
|
|
48
48
|
"@opengeni/capabilities": "^0.3.1",
|
|
49
|
-
"@opengeni/codemode": "^0.4.
|
|
50
|
-
"@opengeni/codex": "^0.2.
|
|
51
|
-
"@opengeni/config": "^0.22.
|
|
52
|
-
"@opengeni/contracts": "^2.
|
|
53
|
-
"@opengeni/core": "^2.
|
|
54
|
-
"@opengeni/db": "^3.
|
|
55
|
-
"@opengeni/documents": "^0.8.
|
|
56
|
-
"@opengeni/events": "^0.4.
|
|
57
|
-
"@opengeni/github": "^0.6.
|
|
49
|
+
"@opengeni/codemode": "^0.4.22",
|
|
50
|
+
"@opengeni/codex": "^0.2.20",
|
|
51
|
+
"@opengeni/config": "^0.22.5",
|
|
52
|
+
"@opengeni/contracts": "^2.9.2",
|
|
53
|
+
"@opengeni/core": "^2.6.4",
|
|
54
|
+
"@opengeni/db": "^3.7.4",
|
|
55
|
+
"@opengeni/documents": "^0.8.14",
|
|
56
|
+
"@opengeni/events": "^0.4.12",
|
|
57
|
+
"@opengeni/github": "^0.6.7",
|
|
58
58
|
"@opengeni/network": "^0.2.3",
|
|
59
|
-
"@opengeni/observability": "^0.8.
|
|
60
|
-
"@opengeni/runtime": "^
|
|
61
|
-
"@opengeni/storage": "^0.2.
|
|
59
|
+
"@opengeni/observability": "^0.8.14",
|
|
60
|
+
"@opengeni/runtime": "^2.0.1",
|
|
61
|
+
"@opengeni/storage": "^0.2.115",
|
|
62
62
|
"@opengeni/xai-subscription": "^0.1.2",
|
|
63
63
|
"@temporalio/client": "^1.17.0",
|
|
64
64
|
"better-auth": "^1.6.14",
|
package/src/app.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
withSessionRlsActorContext,
|
|
45
45
|
} from "@opengeni/db";
|
|
46
46
|
import { requireSessionEventDurableFanoutCapability } from "@opengeni/events";
|
|
47
|
+
import { githubAppBotIdentityWarnings } from "@opengeni/github";
|
|
47
48
|
import { createObservability } from "@opengeni/observability";
|
|
48
49
|
import { createObjectStorage } from "@opengeni/storage";
|
|
49
50
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
@@ -70,14 +71,28 @@ import {
|
|
|
70
71
|
SessionAuthorizationDeniedError,
|
|
71
72
|
SessionAuthorizationUnavailableError,
|
|
72
73
|
} from "@opengeni/core";
|
|
73
|
-
import { createManagedAuth } from "./auth/managed-auth";
|
|
74
74
|
import {
|
|
75
|
+
createManagedAuth,
|
|
76
|
+
isolatedManagedAuthOAuthCallbackRequest,
|
|
77
|
+
managedAuthOAuthReturnMatches,
|
|
78
|
+
resolveManagedAuthOAuthAttempt,
|
|
79
|
+
} from "./auth/managed-auth";
|
|
80
|
+
import {
|
|
81
|
+
adoptManagedAuthSession,
|
|
75
82
|
MANAGED_AUTH_SESSION_SET_COOKIE,
|
|
76
83
|
ManagedAuthActorChangeError,
|
|
84
|
+
managedAuthCsrfHash,
|
|
85
|
+
managedAuthDerivedUuid,
|
|
86
|
+
managedAuthSecretRequestDigest,
|
|
77
87
|
managedAuthSha256,
|
|
78
88
|
} from "@opengeni/core/managed-auth-session-sets";
|
|
79
89
|
import { createBetterAuthSessionAdapter } from "./auth/managed-auth-session-adapter";
|
|
80
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
currentManagedAuthCreatedSessionId,
|
|
92
|
+
runManagedAuthAttempt,
|
|
93
|
+
runManagedAuthDiscardedProviderSession,
|
|
94
|
+
runManagedAuthProvider,
|
|
95
|
+
} from "./auth/managed-auth-attempt-context";
|
|
81
96
|
import { createManagedEmailTransport } from "./auth/managed-email";
|
|
82
97
|
import { assertManagedEmailTransportMetadata } from "./auth/organization-user-setup";
|
|
83
98
|
import { createApiSandboxClient, makeResumeBoxById } from "./sandbox/access";
|
|
@@ -579,26 +594,111 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
579
594
|
registerManagedAuthSessionSetRoutes(app, routeDeps);
|
|
580
595
|
if (managedAuth) {
|
|
581
596
|
app.on(["GET", "POST"], "/v1/auth/*", async (c) => {
|
|
597
|
+
const pathname = new URL(c.req.url).pathname;
|
|
598
|
+
const oauthCallbackProvider = managedAuthOAuthCallbackProvider(pathname);
|
|
582
599
|
if (deps.settings.managedAuthSessionSetMode === "legacy") {
|
|
583
|
-
return
|
|
600
|
+
return oauthCallbackProvider
|
|
601
|
+
? await runManagedAuthProvider(
|
|
602
|
+
oauthCallbackProvider,
|
|
603
|
+
async () => await managedAuth.handler(c.req.raw),
|
|
604
|
+
)
|
|
605
|
+
: await managedAuth.handler(c.req.raw);
|
|
584
606
|
}
|
|
585
|
-
requireManagedAuthProviderRouteAllowed(c.req.method,
|
|
607
|
+
requireManagedAuthProviderRouteAllowed(c.req.method, pathname);
|
|
586
608
|
// Provider authentication/recovery is isolated from whichever actor the
|
|
587
609
|
// browser currently renders. Selected-session capabilities are all
|
|
588
610
|
// product-owned above this wildcard and generation/epoch fenced.
|
|
589
|
-
const headers = new Headers(c.req.raw.headers);
|
|
590
|
-
headers.delete("cookie");
|
|
591
|
-
headers.delete("authorization");
|
|
592
|
-
headers.delete("x-forwarded-user");
|
|
593
|
-
const providerRequest = new Request(c.req.raw, { headers });
|
|
594
611
|
const authority = getCookie(c, MANAGED_AUTH_SESSION_SET_COOKIE);
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
612
|
+
let providerResponse: Response;
|
|
613
|
+
let preserveCookieNames: readonly string[] | undefined;
|
|
614
|
+
if (oauthCallbackProvider) {
|
|
615
|
+
const attempt = await resolveManagedAuthOAuthAttempt(
|
|
616
|
+
managedAuth,
|
|
617
|
+
c.req.raw,
|
|
618
|
+
oauthCallbackProvider,
|
|
619
|
+
deps.settings.publicBaseUrl!,
|
|
620
|
+
);
|
|
621
|
+
if (!attempt || !authority || attempt.authorityHash !== managedAuthSha256(authority)) {
|
|
622
|
+
throw new HTTPException(409, { message: "provider_route_blocked" });
|
|
623
|
+
}
|
|
624
|
+
const isolated = await isolatedManagedAuthOAuthCallbackRequest(managedAuth, c.req.raw);
|
|
625
|
+
preserveCookieNames = [isolated.stateCookieName];
|
|
626
|
+
const handled = await runManagedAuthAttempt(
|
|
627
|
+
attempt.transactionId,
|
|
628
|
+
oauthCallbackProvider,
|
|
629
|
+
async () => {
|
|
630
|
+
const response = await managedAuth.handler(isolated.request);
|
|
631
|
+
return {
|
|
632
|
+
response,
|
|
633
|
+
authSessionId: currentManagedAuthCreatedSessionId(),
|
|
634
|
+
};
|
|
635
|
+
},
|
|
636
|
+
);
|
|
637
|
+
providerResponse = handled.response;
|
|
638
|
+
if (
|
|
639
|
+
!handled.authSessionId &&
|
|
640
|
+
managedAuthOAuthReturnMatches(
|
|
641
|
+
providerResponse.headers.get("location") ?? "",
|
|
642
|
+
new URL(deps.settings.publicBaseUrl!).origin,
|
|
643
|
+
attempt.transactionId,
|
|
644
|
+
"complete",
|
|
600
645
|
)
|
|
601
|
-
|
|
646
|
+
) {
|
|
647
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl!);
|
|
648
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
649
|
+
location.searchParams.set("social", "error");
|
|
650
|
+
providerResponse = Response.redirect(location, 302);
|
|
651
|
+
} else if (handled.authSessionId) {
|
|
652
|
+
try {
|
|
653
|
+
await adoptManagedAuthSession({
|
|
654
|
+
db: deps.db,
|
|
655
|
+
adapter: managedAuthSessionAdapter!,
|
|
656
|
+
authority,
|
|
657
|
+
authorityHash: attempt.authorityHash,
|
|
658
|
+
csrfHash: managedAuthCsrfHash(authority),
|
|
659
|
+
operationId: managedAuthDerivedUuid(
|
|
660
|
+
"opengeni:managed-auth:social-completion",
|
|
661
|
+
`${attempt.transactionId}:${attempt.provider}`,
|
|
662
|
+
),
|
|
663
|
+
requestDigest: managedAuthSecretRequestDigest(deps.settings.betterAuthSecret!, {
|
|
664
|
+
operation: "social_completion",
|
|
665
|
+
transactionId: attempt.transactionId,
|
|
666
|
+
provider: attempt.provider,
|
|
667
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
668
|
+
expectedActorEpoch: attempt.expectedActorEpoch,
|
|
669
|
+
}),
|
|
670
|
+
expectedGeneration: attempt.expectedGeneration,
|
|
671
|
+
expectedActorEpoch: attempt.expectedActorEpoch,
|
|
672
|
+
transactionId: attempt.transactionId,
|
|
673
|
+
transactionSecretHash: attempt.transactionSecretHash,
|
|
674
|
+
authSessionId: handled.authSessionId,
|
|
675
|
+
mode: deps.settings.managedAuthSessionSetMode,
|
|
676
|
+
});
|
|
677
|
+
} catch {
|
|
678
|
+
// adoptManagedAuthSession revokes only when durable reconciliation
|
|
679
|
+
// proves that completion did not commit. An uncertain outcome may
|
|
680
|
+
// already point the selected slot at this session, so the callback
|
|
681
|
+
// must not independently revoke it.
|
|
682
|
+
const location = new URL("/account-auth", deps.settings.publicBaseUrl!);
|
|
683
|
+
location.searchParams.set("transaction", attempt.transactionId);
|
|
684
|
+
location.searchParams.set("social", "error");
|
|
685
|
+
providerResponse = Response.redirect(location, 302);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
const headers = new Headers(c.req.raw.headers);
|
|
690
|
+
headers.delete("cookie");
|
|
691
|
+
headers.delete("authorization");
|
|
692
|
+
headers.delete("x-forwarded-user");
|
|
693
|
+
const providerRequest = new Request(c.req.raw, { headers });
|
|
694
|
+
const discardProviderSession =
|
|
695
|
+
deps.settings.managedAuthSessionSetMode === "broker" || authority !== undefined;
|
|
696
|
+
providerResponse = discardProviderSession
|
|
697
|
+
? await runManagedAuthDiscardedProviderSession(
|
|
698
|
+
async () => await managedAuth.handler(providerRequest),
|
|
699
|
+
)
|
|
700
|
+
: await managedAuth.handler(providerRequest);
|
|
701
|
+
}
|
|
602
702
|
let replacementCookies: readonly string[] | undefined;
|
|
603
703
|
if (deps.settings.managedAuthSessionSetMode === "broker") {
|
|
604
704
|
replacementCookies = await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
|
|
@@ -628,19 +728,24 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
628
728
|
}
|
|
629
729
|
}
|
|
630
730
|
}
|
|
631
|
-
return await scrubManagedAuthProviderResponse(providerResponse, {
|
|
731
|
+
return await scrubManagedAuthProviderResponse(providerResponse, {
|
|
732
|
+
replacementCookies,
|
|
733
|
+
preserveCookieNames,
|
|
734
|
+
});
|
|
632
735
|
});
|
|
633
736
|
}
|
|
634
737
|
|
|
635
|
-
app.get("/healthz", (c) =>
|
|
636
|
-
|
|
738
|
+
app.get("/healthz", (c) => {
|
|
739
|
+
const warnings = githubAppBotIdentityWarnings(deps.settings);
|
|
740
|
+
return c.json({
|
|
637
741
|
service: deps.settings.serviceName,
|
|
638
742
|
environment: deps.settings.environment,
|
|
639
743
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
640
744
|
...(deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {}),
|
|
745
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
641
746
|
ok: true,
|
|
642
|
-
})
|
|
643
|
-
);
|
|
747
|
+
});
|
|
748
|
+
});
|
|
644
749
|
|
|
645
750
|
app.get("/readyz", async (c) => {
|
|
646
751
|
const result = await runReadinessChecks(readinessChecks(deps), 2_000);
|
|
@@ -681,6 +786,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
681
786
|
models: configuredModels(catalogSettings).map(projectClientModel),
|
|
682
787
|
defaultReasoningEffort: deps.settings.openaiReasoningEffort,
|
|
683
788
|
allowedReasoningEfforts: configuredAllowedReasoningEfforts(deps.settings),
|
|
789
|
+
defaultSandboxBackend: deps.settings.sandboxBackend,
|
|
684
790
|
mcpServers: deps.settings.mcpServers.map((server) => ({
|
|
685
791
|
id: server.id,
|
|
686
792
|
name: server.name ?? server.id,
|
|
@@ -956,6 +1062,11 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
956
1062
|
return { app, routeDeps };
|
|
957
1063
|
}
|
|
958
1064
|
|
|
1065
|
+
function managedAuthOAuthCallbackProvider(pathname: string): "google" | "github" | null {
|
|
1066
|
+
const match = pathname.match(/^\/v1\/auth\/callback\/(google|github)$/u);
|
|
1067
|
+
return match?.[1] === "google" || match?.[1] === "github" ? match[1] : null;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
959
1070
|
function mutationOutcomeUnknown(error: unknown, method: string): boolean {
|
|
960
1071
|
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return false;
|
|
961
1072
|
const cause = error instanceof HTTPException ? error.cause : error;
|
|
@@ -1072,6 +1183,14 @@ function clientAuthConfig(settings: AppDependencies["settings"]) {
|
|
|
1072
1183
|
mode: "managedSession" as const,
|
|
1073
1184
|
session: "cookie" as const,
|
|
1074
1185
|
emailVerificationRequired: settings.environment !== "local",
|
|
1186
|
+
socialProviders: [
|
|
1187
|
+
...(settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret
|
|
1188
|
+
? (["google"] as const)
|
|
1189
|
+
: []),
|
|
1190
|
+
...(settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret
|
|
1191
|
+
? (["github"] as const)
|
|
1192
|
+
: []),
|
|
1193
|
+
],
|
|
1075
1194
|
};
|
|
1076
1195
|
}
|
|
1077
1196
|
if (settings.productAccessMode === "configured") {
|
|
@@ -1354,6 +1473,22 @@ const routeLabelPatterns: Array<{
|
|
|
1354
1473
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/,
|
|
1355
1474
|
label: "/v1/workspaces/:workspaceId/codex/status",
|
|
1356
1475
|
},
|
|
1476
|
+
{
|
|
1477
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/source$/,
|
|
1478
|
+
label: "/v1/workspaces/:workspaceId/codex/source",
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/(accounts|settings)$/,
|
|
1482
|
+
label: (match) => `/v1/organizations/:organizationId/codex/${match[1]}`,
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/connect\/(start|poll)$/,
|
|
1486
|
+
label: (match) => `/v1/organizations/:organizationId/codex/connect/${match[1]}`,
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
pattern: /^\/v1\/organizations\/[^/]+\/codex\/accounts\/[^/]+(?:\/activate)?$/,
|
|
1490
|
+
label: "/v1/organizations/:organizationId/codex/accounts/:accountId",
|
|
1491
|
+
},
|
|
1357
1492
|
{
|
|
1358
1493
|
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/connect\/(start|poll)$/,
|
|
1359
1494
|
label: (match) => `/v1/workspaces/:workspaceId/supergrok/connect/${match[1]}`,
|
|
@@ -1513,6 +1648,11 @@ const routeLabelPatterns: Array<{
|
|
|
1513
1648
|
label:
|
|
1514
1649
|
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/observation",
|
|
1515
1650
|
},
|
|
1651
|
+
{
|
|
1652
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/targets\/[^/]+\/screenshot$/,
|
|
1653
|
+
label:
|
|
1654
|
+
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/screenshot",
|
|
1655
|
+
},
|
|
1516
1656
|
{
|
|
1517
1657
|
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/operations\/[^/]+$/,
|
|
1518
1658
|
label:
|
|
@@ -1689,6 +1829,18 @@ const routeLabelPatterns: Array<{
|
|
|
1689
1829
|
pattern: /^\/v1\/workspaces\/[^/]+\/api-keys\/[^/]+$/,
|
|
1690
1830
|
label: "/v1/workspaces/:workspaceId/api-keys/:id",
|
|
1691
1831
|
},
|
|
1832
|
+
{
|
|
1833
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys$/,
|
|
1834
|
+
label: "/v1/organizations/:organizationId/api-keys",
|
|
1835
|
+
},
|
|
1836
|
+
{
|
|
1837
|
+
pattern: /^\/v1\/organizations\/[^/]+\/api-keys\/[^/]+$/,
|
|
1838
|
+
label: "/v1/organizations/:organizationId/api-keys/:id",
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
pattern: /^\/v1\/workspaces\/external$/,
|
|
1842
|
+
label: "/v1/workspaces/external",
|
|
1843
|
+
},
|
|
1692
1844
|
{
|
|
1693
1845
|
pattern: /^\/v1\/workspaces\/[^/]+\/scheduled-tasks$/,
|
|
1694
1846
|
label: "/v1/workspaces/:workspaceId/scheduled-tasks",
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
|
|
3
3
|
type ManagedAuthAttemptContext =
|
|
4
|
-
| {
|
|
4
|
+
| {
|
|
5
|
+
kind: "isolated_transaction";
|
|
6
|
+
transactionId: string;
|
|
7
|
+
providerId: "credential" | "google" | "github";
|
|
8
|
+
createdAuthSessionId: string | null;
|
|
9
|
+
}
|
|
10
|
+
| { kind: "provider"; providerId: "credential" | "google" | "github" }
|
|
5
11
|
| { kind: "discard_provider_session" };
|
|
6
12
|
|
|
7
13
|
const managedAuthAttemptStorage = new AsyncLocalStorage<ManagedAuthAttemptContext>();
|
|
8
14
|
|
|
9
|
-
export function runManagedAuthAttempt<T>(
|
|
10
|
-
|
|
15
|
+
export function runManagedAuthAttempt<T>(
|
|
16
|
+
transactionId: string,
|
|
17
|
+
providerId: "credential" | "google" | "github",
|
|
18
|
+
action: () => T,
|
|
19
|
+
): T {
|
|
20
|
+
return managedAuthAttemptStorage.run(
|
|
21
|
+
{ kind: "isolated_transaction", transactionId, providerId, createdAuthSessionId: null },
|
|
22
|
+
action,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function runManagedAuthProvider<T>(
|
|
27
|
+
providerId: "credential" | "google" | "github",
|
|
28
|
+
action: () => T,
|
|
29
|
+
): T {
|
|
30
|
+
return managedAuthAttemptStorage.run({ kind: "provider", providerId }, action);
|
|
11
31
|
}
|
|
12
32
|
|
|
13
33
|
export function runManagedAuthDiscardedProviderSession<T>(action: () => T): T {
|
|
@@ -19,6 +39,23 @@ export function currentManagedAuthAttemptId(): string | null {
|
|
|
19
39
|
return context?.kind === "isolated_transaction" ? context.transactionId : null;
|
|
20
40
|
}
|
|
21
41
|
|
|
42
|
+
export function currentManagedAuthProviderId(): "credential" | "google" | "github" {
|
|
43
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
44
|
+
return context?.kind === "isolated_transaction" || context?.kind === "provider"
|
|
45
|
+
? context.providerId
|
|
46
|
+
: "credential";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function recordCurrentManagedAuthSession(authSessionId: string): void {
|
|
50
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
51
|
+
if (context?.kind === "isolated_transaction") context.createdAuthSessionId = authSessionId;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function currentManagedAuthCreatedSessionId(): string | null {
|
|
55
|
+
const context = managedAuthAttemptStorage.getStore();
|
|
56
|
+
return context?.kind === "isolated_transaction" ? context.createdAuthSessionId : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
22
59
|
export function shouldDiscardCurrentManagedAuthProviderSession(): boolean {
|
|
23
60
|
return managedAuthAttemptStorage.getStore()?.kind === "discard_provider_session";
|
|
24
61
|
}
|