@opengeni/api-router 2.3.2-canary.2 → 2.5.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.
Files changed (44) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/auth/managed-auth-attempt-context.d.ts +4 -0
  3. package/dist/auth/managed-auth-session-adapter.d.ts +4 -0
  4. package/dist/{chunk-IBV7Z6F4.js → chunk-QESX7HDK.js} +3645 -470
  5. package/dist/chunk-QESX7HDK.js.map +1 -0
  6. package/dist/fatal-process-boundary.d.ts +25 -0
  7. package/dist/http/sse.d.ts +2 -0
  8. package/dist/index.d.ts +5 -1
  9. package/dist/index.js +168 -6
  10. package/dist/index.js.map +1 -1
  11. package/dist/integrations/slack-interactions.d.ts +1 -1
  12. package/dist/mcp/receipts.d.ts +9 -0
  13. package/dist/mcp/server.d.ts +33 -0
  14. package/dist/organization-recovery-notifications.d.ts +41 -0
  15. package/dist/routes/managed-auth-session-sets.d.ts +19 -0
  16. package/dist/routes/organization-recovery.d.ts +19 -0
  17. package/dist/routes/sessions.d.ts +17 -5
  18. package/dist/routes/workspaces.d.ts +1 -0
  19. package/dist/work-discovery-observability.d.ts +33 -0
  20. package/package.json +18 -18
  21. package/src/app.ts +133 -1
  22. package/src/auth/managed-auth-attempt-context.ts +24 -0
  23. package/src/auth/managed-auth-session-adapter.ts +205 -0
  24. package/src/auth/managed-auth.ts +52 -2
  25. package/src/fatal-process-boundary.ts +231 -0
  26. package/src/http/sse.ts +7 -0
  27. package/src/index.ts +25 -5
  28. package/src/integrations/slack-interactions.ts +30 -17
  29. package/src/mcp/receipts.ts +34 -0
  30. package/src/mcp/server.ts +349 -68
  31. package/src/organization-recovery-notifications.ts +103 -0
  32. package/src/routes/canonical-human-identities.ts +29 -14
  33. package/src/routes/codex.ts +5 -1
  34. package/src/routes/environments.ts +23 -0
  35. package/src/routes/interaction-resources.ts +3 -0
  36. package/src/routes/managed-auth-session-sets.ts +994 -0
  37. package/src/routes/managed-onboarding.ts +2 -0
  38. package/src/routes/organization-memberships.ts +2 -0
  39. package/src/routes/organization-recovery.ts +325 -0
  40. package/src/routes/sessions.ts +401 -120
  41. package/src/routes/supergrok.ts +5 -1
  42. package/src/routes/workspaces.ts +23 -1
  43. package/src/work-discovery-observability.ts +121 -0
  44. package/dist/chunk-IBV7Z6F4.js.map +0 -1
@@ -19,7 +19,7 @@ export declare const SLACK_SESSION_INSTRUCTIONS: string;
19
19
  * selection at session creation while keeping Slack mutations out of the model
20
20
  * surface; interaction delivery remains owned by the durable delivery pump.
21
21
  */
22
- export declare const SLACK_TASK_FIRST_PARTY_MCP_TOOLS: ("artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_publish" | "artifacts_rollback" | "atlassian_get" | "atlassian_search" | "atlassian_sources_list" | "browser_act" | "browser_auth" | "browser_clipboard" | "browser_debug" | "browser_identity" | "browser_lifecycle" | "browser_observe" | "browser_open" | "browser_publish" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_remove" | "editable_artifact_apply" | "editable_artifact_create" | "editable_artifact_export" | "editable_artifact_export_status" | "editable_artifact_get" | "editable_artifact_import" | "editable_artifact_inspect" | "editable_artifact_list" | "environment_list" | "environment_set_variable" | "fiken_bank_accounts_list" | "fiken_companies_list" | "fiken_contact_create" | "fiken_contacts_list" | "fiken_invoice_draft_create" | "fiken_invoice_get" | "fiken_invoices_list" | "fiken_products_list" | "fiken_purchases_list" | "fiken_sales_list" | "github_connect_link" | "github_repositories_list" | "goal_complete" | "goal_pause" | "goal_progress" | "goal_set" | "goal_update" | "goal_wait" | "instruction_policy_propose" | "interaction_discover" | "interaction_request_human" | "knowledge_correct" | "knowledge_propose" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "remember" | "remember_confirm" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "sandbox_file_publish" | "sandbox_provision" | "sandbox_swap" | "sandboxes_list" | "scheduled_task_runs_list" | "scheduled_tasks_create" | "scheduled_tasks_delete" | "scheduled_tasks_get" | "scheduled_tasks_list" | "scheduled_tasks_pause" | "scheduled_tasks_resume" | "scheduled_tasks_trigger" | "scheduled_tasks_update" | "session_create" | "session_events" | "session_get" | "session_human_input_respond" | "session_pause" | "session_resume" | "session_send_message" | "session_steer" | "session_wait" | "sessions_list" | "set_other_session_title" | "set_session_title" | "slack_bot_channel_history" | "slack_bot_delete_message" | "slack_bot_file_content" | "slack_bot_file_info" | "slack_bot_list_channels" | "slack_bot_list_files" | "slack_bot_list_users" | "slack_bot_post_message" | "slack_bot_search" | "slack_bot_thread_replies" | "social_connections_list" | "social_daily_analysis_context" | "social_mentions_live" | "social_post_reply" | "social_posts_recent" | "social_posts_sync" | "social_search_live" | "social_thread_fetch" | "task_note_archive" | "task_note_promote_instruction_policy" | "task_note_promote_knowledge" | "task_note_promote_preference" | "task_note_replace" | "task_note_save" | "task_notes_list" | "variable_set_get_variable" | "variable_set_list" | "variable_set_set_variable" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
22
+ export declare const SLACK_TASK_FIRST_PARTY_MCP_TOOLS: ("artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_publish" | "artifacts_rollback" | "atlassian_get" | "atlassian_search" | "atlassian_sources_list" | "browser_act" | "browser_auth" | "browser_clipboard" | "browser_debug" | "browser_identity" | "browser_lifecycle" | "browser_observe" | "browser_open" | "browser_publish" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_remove" | "editable_artifact_apply" | "editable_artifact_create" | "editable_artifact_export" | "editable_artifact_export_status" | "editable_artifact_get" | "editable_artifact_import" | "editable_artifact_inspect" | "editable_artifact_list" | "environment_list" | "environment_set_variable" | "fiken_bank_accounts_list" | "fiken_companies_list" | "fiken_contact_create" | "fiken_contacts_list" | "fiken_invoice_draft_create" | "fiken_invoice_get" | "fiken_invoices_list" | "fiken_products_list" | "fiken_purchases_list" | "fiken_sales_list" | "github_connect_link" | "github_repositories_list" | "goal_complete" | "goal_pause" | "goal_progress" | "goal_set" | "goal_update" | "goal_wait" | "instruction_policy_propose" | "interaction_discover" | "interaction_request_human" | "knowledge_correct" | "knowledge_propose" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "remember" | "remember_confirm" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "sandbox_file_publish" | "sandbox_provision" | "sandbox_swap" | "sandboxes_list" | "scheduled_task_runs_list" | "scheduled_tasks_create" | "scheduled_tasks_delete" | "scheduled_tasks_get" | "scheduled_tasks_list" | "scheduled_tasks_pause" | "scheduled_tasks_resume" | "scheduled_tasks_trigger" | "scheduled_tasks_update" | "session_create" | "session_events" | "session_get" | "session_human_input_respond" | "session_pause" | "session_resume" | "session_send_message" | "session_steer" | "session_wait" | "sessions_list" | "set_other_session_title" | "set_session_title" | "slack_bot_channel_history" | "slack_bot_delete_message" | "slack_bot_file_content" | "slack_bot_file_info" | "slack_bot_list_channels" | "slack_bot_list_files" | "slack_bot_list_users" | "slack_bot_post_message" | "slack_bot_search" | "slack_bot_thread_replies" | "social_connections_list" | "social_daily_analysis_context" | "social_mentions_live" | "social_post_reply" | "social_posts_recent" | "social_posts_sync" | "social_search_live" | "social_thread_fetch" | "task_note_archive" | "task_note_promote_instruction_policy" | "task_note_promote_knowledge" | "task_note_promote_preference" | "task_note_replace" | "task_note_save" | "task_notes_list" | "variable_set_get_variable" | "variable_set_list" | "variable_set_set_variable" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
23
23
  export type NormalizedSlackInteraction = {
24
24
  providerEventId: string;
25
25
  providerMessageId: string;
@@ -9,6 +9,15 @@ export type McpMutationReceiptInput = Omit<McpMutationReceiptType, "receiptVersi
9
9
  * accidentally adding an unbounded entity or a copy of request fields.
10
10
  */
11
11
  export declare function mcpMutationReceipt(input: McpMutationReceiptInput): McpMutationReceiptType;
12
+ export declare function sessionControlMutationReceipt(input: {
13
+ operation: "session_pause" | "session_resume";
14
+ sessionId: string;
15
+ state: string;
16
+ receiptId: string;
17
+ timestamp: string;
18
+ outcome: "changed" | "unchanged" | "replayed";
19
+ interruptionCount: number;
20
+ }): McpMutationReceiptType;
12
21
  export type SessionCreateReceiptResult = {
13
22
  session: {
14
23
  id: string;
@@ -94,6 +94,39 @@ export declare function capSessionDiscoveryPage(page: Awaited<ReturnType<typeof
94
94
  attentionSince?: string | null | undefined;
95
95
  truncated: boolean;
96
96
  };
97
+ relatedWork: {
98
+ claims: {
99
+ id: string;
100
+ sessionId: string;
101
+ subject: {
102
+ namespace: string;
103
+ type: "artifact" | "branch" | "ci_run" | "issue" | "other" | "pull_request" | "release" | "repository";
104
+ canonicalKey: string;
105
+ displayLabel: string | null;
106
+ };
107
+ role: "delivering" | "monitoring" | "reviewing" | "working";
108
+ state: "active" | "released" | "stale" | "superseded";
109
+ revision: number;
110
+ provenance: "explicit_agent" | "session_resource" | "system_lifecycle" | "trusted_integration" | "user_api";
111
+ version: {
112
+ kind: "artifact_version" | "branch_head" | "ci_run" | "git_commit" | "other" | "pull_request_head" | "release_version";
113
+ value: string;
114
+ } | null;
115
+ observedAt: string;
116
+ updatedAt: string;
117
+ settledAt: string | null;
118
+ }[];
119
+ claimsTruncated: boolean;
120
+ match: {
121
+ class: "exact_subject" | "fuzzy" | "goal" | "title";
122
+ field: "claim_key" | "claim_label" | "goal" | "subject" | "title";
123
+ scoreBand: "exact" | "related" | "strong";
124
+ claimId: string | null;
125
+ } | null;
126
+ possibleOverlap: boolean;
127
+ advisoryOnly: true;
128
+ noAdditionalAccess: true;
129
+ };
97
130
  latestMessage?: {
98
131
  type: "agent.message.completed" | "agent.message.delta" | "agent.model.request" | "agent.model.usage" | "agent.reasoning.delta" | "agent.toolCall.created" | "agent.toolCall.output" | "agent.updated" | "artifact.created" | "codex.account.switched" | "codex.capacity.resumed" | "codex.capacity.superseded" | "codex.capacity.waiting" | "codex.credential.selected" | "codex.fleet.decision" | "credential.auth_needed" | "fs.changed" | "git.changed" | "goal.cleared" | "goal.completed" | "goal.continuation" | "goal.held" | "goal.paused" | "goal.progress" | "goal.resumed" | "goal.rewrite.proposed" | "goal.rewrite.rejected" | "goal.set" | "goal.updated" | "machine.link.lost" | "machine.link.restored" | "machine.op.failed" | "machine.op.recovered" | "machine.runner.restarted" | "memory.corrected" | "memory.saved" | "recording.available" | "recording.failed" | "recording.started" | "rig.setup.completed" | "rig.setup.failed" | "rig.setup.skipped" | "rig.setup.started" | "sandbox.box.created" | "sandbox.box.lost" | "sandbox.box.snapshot" | "sandbox.box.terminated" | "sandbox.command.output.delta" | "sandbox.env.drift" | "sandbox.operation.completed" | "sandbox.operation.failed" | "sandbox.operation.started" | "session.command.backgrounded" | "session.command.finished" | "session.context.cleared" | "session.context.compacted" | "session.context.compaction.requested" | "session.context.compaction.skipped" | "session.context.compaction.started" | "session.control.paused" | "session.control.resumed" | "session.control.steer_requested" | "session.created" | "session.event.envelope_omitted" | "session.humanInput.requested" | "session.mcp.approval_policy.updated" | "session.personal_resources.attached" | "session.queue.changed" | "session.queue.history" | "session.queue.prompt.cancelled" | "session.realtime.ended" | "session.realtime.started" | "session.requiresAction" | "session.route.reconciled" | "session.runtime.configured" | "session.status.changed" | "session.title_set" | "session.tool_policy.updated" | "session.variable_sets.updated" | "session.visibility.changed" | "stream.closed" | "stream.opened" | "stream.revoked" | "stream.url.rotated" | "system.update.cancelled" | "system.update.delivered" | "system.update.pending" | "system.update.settled" | "system.update.superseded" | "terminal.pty.exited" | "terminal.pty.output.delta" | "terminal.pty.started" | "tool.auth_needed" | "turn.cancelled" | "turn.capacity_waiting" | "turn.completed" | "turn.event.rejected_late" | "turn.failed" | "turn.queued" | "turn.recovery.requested" | "turn.started" | "turn.startup.phase.completed" | "turn.startup.phase.failed" | "turn.startup.phase.started" | "turn.superseded" | "user.approvalDecision" | "user.humanInputResponse" | "user.message" | "user.pause" | "workspace.inference.paused" | "workspace.inference.resumed" | "workspace.revision.captured" | "workspace.revision.degraded";
99
132
  preview: string | null;
@@ -0,0 +1,41 @@
1
+ import type { Database } from "@opengeni/db";
2
+ import { prepareOrganizationRecoveryNotifications, settleOrganizationRecoveryNotification, type OrganizationRecoveryNotificationClaim, type OrganizationRecoveryNotificationSettlement } from "@opengeni/db";
3
+ export type OrganizationRecoveryNotificationDeliveryResult = {
4
+ status: "sent";
5
+ providerMessageId: string | null;
6
+ } | {
7
+ status: "failed";
8
+ errorClass: string;
9
+ } | {
10
+ status: "outcome_unknown";
11
+ };
12
+ export interface OrganizationRecoveryNotificationTransport {
13
+ readonly provider: string;
14
+ send(claim: OrganizationRecoveryNotificationClaim): Promise<OrganizationRecoveryNotificationDeliveryResult>;
15
+ }
16
+ export type OrganizationRecoveryNotificationLifecycle = {
17
+ prepare: typeof prepareOrganizationRecoveryNotifications;
18
+ settle: typeof settleOrganizationRecoveryNotification;
19
+ };
20
+ export declare function dispatchOrganizationRecoveryNotifications(input: {
21
+ db: Database;
22
+ transport: OrganizationRecoveryNotificationTransport;
23
+ claimOwner: string;
24
+ limit?: number;
25
+ leaseSeconds?: number;
26
+ lifecycle?: OrganizationRecoveryNotificationLifecycle;
27
+ }): Promise<OrganizationRecoveryNotificationSettlement[]>;
28
+ export declare class InMemoryOrganizationRecoveryNotificationTransport implements OrganizationRecoveryNotificationTransport {
29
+ readonly provider = "fake";
30
+ readonly attempts: Array<{
31
+ idempotencyKey: string;
32
+ payloadDigest: string;
33
+ recipientCanonicalIdentityId: string;
34
+ providerMessageId: string;
35
+ }>;
36
+ private readonly deliveries;
37
+ private readonly scripted;
38
+ enqueue(...results: OrganizationRecoveryNotificationDeliveryResult[]): void;
39
+ logicalDeliveryCount(): number;
40
+ send(claim: OrganizationRecoveryNotificationClaim): Promise<OrganizationRecoveryNotificationDeliveryResult>;
41
+ }
@@ -0,0 +1,19 @@
1
+ import { type ApiRouteDeps } from "@opengeni/core";
2
+ import type { Hono } from "hono";
3
+ export declare function registerManagedAuthSessionSetRoutes(app: Hono, deps: ApiRouteDeps): void;
4
+ /**
5
+ * Better Auth 1.6.26 provider lifecycle routes retained while session-set mode
6
+ * owns all selected-session reads, enumeration, revocation, and user mutation.
7
+ * The list is intentionally exact so a dependency upgrade cannot silently add
8
+ * a selected-session capability under the wildcard.
9
+ */
10
+ export declare function requireManagedAuthProviderRouteAllowed(method: string, pathname: string): void;
11
+ /** Remove provider bearer/session material and enforce browser-auth cache/cookie policy. */
12
+ export declare function scrubManagedAuthProviderResponse(response: Response, options?: {
13
+ replacementCookies?: readonly string[] | undefined;
14
+ }): Promise<Response>;
15
+ export declare function parseSupportedDeepLink(path: string): {
16
+ workspaceId: string | null;
17
+ sessionId: string | null;
18
+ permission: "workspace:read" | "sessions:read";
19
+ } | null;
@@ -0,0 +1,19 @@
1
+ import { getManagedAuthRequestActorAdmissionStamp, getManagedAuthRequestActorLeaseStamp, requireCanonicalHumanRequestIdentity } from "@opengeni/core/canonical-human-identities";
2
+ import type { ApiRouteDeps } from "@opengeni/core";
3
+ import { acceptOrganizationRecoveryCustody, approveOrganizationRecoveryOperation, cancelOrganizationRecoveryOperation, configureOrganizationRecoveryPolicy, disableOrganizationRecoveryPolicy, executeOrganizationRecoveryOperation, getOrganizationRecoveryOverview, startOrganizationRecoveryOperation } from "@opengeni/db";
4
+ import type { Hono } from "hono";
5
+ export type OrganizationRecoveryRouteServices = {
6
+ requireCanonicalHumanRequestIdentity: typeof requireCanonicalHumanRequestIdentity;
7
+ getManagedAuthRequestActorAdmissionStamp: typeof getManagedAuthRequestActorAdmissionStamp;
8
+ getManagedAuthRequestActorLeaseStamp: typeof getManagedAuthRequestActorLeaseStamp;
9
+ getOrganizationRecoveryOverview: typeof getOrganizationRecoveryOverview;
10
+ configureOrganizationRecoveryPolicy: typeof configureOrganizationRecoveryPolicy;
11
+ acceptOrganizationRecoveryCustody: typeof acceptOrganizationRecoveryCustody;
12
+ disableOrganizationRecoveryPolicy: typeof disableOrganizationRecoveryPolicy;
13
+ startOrganizationRecoveryOperation: typeof startOrganizationRecoveryOperation;
14
+ approveOrganizationRecoveryOperation: typeof approveOrganizationRecoveryOperation;
15
+ cancelOrganizationRecoveryOperation: typeof cancelOrganizationRecoveryOperation;
16
+ executeOrganizationRecoveryOperation: typeof executeOrganizationRecoveryOperation;
17
+ };
18
+ export declare function organizationRecoveryHttpError(error: unknown): Error;
19
+ export declare function registerOrganizationRecoveryRoutes(app: Hono, deps: ApiRouteDeps, services?: OrganizationRecoveryRouteServices): void;
@@ -1,4 +1,4 @@
1
- import { ClientSessionEvent, SteerSessionMessageRequest, type SessionGoalRevision, type SessionAuthorizationOperation } from "@opengeni/contracts";
1
+ import { ClientSessionEvent, SteerSessionMessageRequest, type SessionStatus, type WorkClaimSubjectFilter, type SessionGoalRevision, type SessionAuthorizationOperation } from "@opengeni/contracts";
2
2
  import { type SessionDiscoveryCursor } from "@opengeni/db";
3
3
  import type { Context, Hono } from "hono";
4
4
  import type { ApiRouteDeps } from "@opengeni/core";
@@ -15,14 +15,26 @@ export declare function sessionAuthorizationOperationForHttp(method: string, pat
15
15
  export declare function sessionTenancyHttpError(error: unknown): Error;
16
16
  export type AgentTopologyCursorEnvelope = {
17
17
  cursor: SessionDiscoveryCursor;
18
- parentSessionId: string | null;
19
- search: string | null;
18
+ parentSessionId: string | null | "all";
19
+ rootSessionId: string | null;
20
+ query: string | null;
21
+ statuses: SessionStatus[];
22
+ activeOnly: boolean;
23
+ recentHours: number | null;
24
+ subject: WorkClaimSubjectFilter | null;
25
+ claimLimit: number | null;
20
26
  };
21
27
  export declare function encodeAgentTopologyCursor(value: AgentTopologyCursorEnvelope): string;
22
28
  export declare function agentTopologyQuery(query: Record<string, string>): {
23
29
  limit: number;
24
- parentSessionId: string | null;
25
- search: string | undefined;
30
+ parentSessionId: string | null | undefined;
31
+ rootSessionId: string | undefined;
32
+ query: string | undefined;
33
+ statuses: SessionStatus[] | undefined;
34
+ activeOnly: boolean;
35
+ recentHours: number | undefined;
36
+ subject: WorkClaimSubjectFilter | undefined;
37
+ claimLimit: number | undefined;
26
38
  cursor: SessionDiscoveryCursor | undefined;
27
39
  };
28
40
  /** Stable, value-free JSON errors for only the create-session boundary. */
@@ -20,5 +20,6 @@ export declare function workspaceMembersResponse(members: readonly WorkspaceMemb
20
20
  createdAt: string;
21
21
  }[];
22
22
  };
23
+ export declare function workspaceUpdateRequestsAccountTransfer(value: unknown): boolean;
23
24
  export declare function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void;
24
25
  export {};
@@ -0,0 +1,33 @@
1
+ import type { WorkDiscoveryMatchClass } from "@opengeni/contracts";
2
+ import type { Observability } from "@opengeni/observability";
3
+ export type WorkDiscoverySurface = "first_party_mcp" | "agent_topology";
4
+ export type WorkDiscoveryMode = "browse" | "query" | "subject";
5
+ export type WorkDiscoveryOutcome = "ok" | "empty" | "disabled" | "error";
6
+ export type WorkDiscoveryAuthorizationScope = "workspace" | "scoped";
7
+ export type WorkDiscoveryObservation = {
8
+ surface: WorkDiscoverySurface;
9
+ mode: WorkDiscoveryMode;
10
+ outcome: WorkDiscoveryOutcome;
11
+ authorizationScope: WorkDiscoveryAuthorizationScope;
12
+ durationMs: number;
13
+ responseBytes: number;
14
+ resultCount: number;
15
+ overlapCount: number;
16
+ matchCounts: Partial<Record<WorkDiscoveryMatchClass, number>>;
17
+ };
18
+ type WorkDiscoveryObservedRow = {
19
+ relatedWork: {
20
+ match: {
21
+ class: WorkDiscoveryMatchClass;
22
+ } | null;
23
+ possibleOverlap: boolean;
24
+ };
25
+ };
26
+ export declare function summarizeWorkDiscoveryRows(rows: readonly WorkDiscoveryObservedRow[]): Pick<WorkDiscoveryObservation, "resultCount" | "overlapCount" | "matchCounts">;
27
+ /**
28
+ * Low-cardinality work-discovery telemetry. Identifiers, search text, subject
29
+ * keys, titles, goals, claim labels, versions, and provenance are excluded by
30
+ * construction. An observability failure never changes product behavior.
31
+ */
32
+ export declare function observeWorkDiscovery(observability: Observability | null | undefined, observation: WorkDiscoveryObservation): void;
33
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "2.3.2-canary.2",
3
+ "version": "2.5.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": {
@@ -43,23 +43,23 @@
43
43
  "@hono/zod-validator": "^0.7.6",
44
44
  "@llamaindex/liteparse": "^1.5.3",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
- "@opengeni/agent-proto": "^0.5.1-canary.4",
47
- "@opengeni/artifact-tool": "^0.3.7-canary.2",
48
- "@opengeni/capabilities": "^0.3.1-canary.3",
49
- "@opengeni/codemode": "^0.4.15-canary.2",
50
- "@opengeni/codex": "^0.2.19-canary.3",
51
- "@opengeni/config": "^0.20.1-canary.2",
52
- "@opengeni/contracts": "^2.5.0-canary.2",
53
- "@opengeni/core": "^2.4.0-canary.2",
54
- "@opengeni/db": "^3.4.0-canary.2",
55
- "@opengeni/documents": "^0.8.2-canary.2",
56
- "@opengeni/events": "^0.4.0-canary.2",
57
- "@opengeni/github": "^0.5.5-canary.2",
58
- "@opengeni/network": "^0.2.3-canary.3",
59
- "@opengeni/observability": "^0.8.6-canary.2",
60
- "@opengeni/runtime": "^1.4.0-canary.2",
61
- "@opengeni/storage": "^0.2.108-canary.2",
62
- "@opengeni/xai-subscription": "^0.1.2-canary.3",
46
+ "@opengeni/agent-proto": "^0.5.1",
47
+ "@opengeni/artifact-tool": "^0.3.9",
48
+ "@opengeni/capabilities": "^0.3.1",
49
+ "@opengeni/codemode": "^0.4.17",
50
+ "@opengeni/codex": "^0.2.19",
51
+ "@opengeni/config": "^0.22.0",
52
+ "@opengeni/contracts": "^2.7.0",
53
+ "@opengeni/core": "^2.5.3",
54
+ "@opengeni/db": "^3.6.0",
55
+ "@opengeni/documents": "^0.8.6",
56
+ "@opengeni/events": "^0.4.4",
57
+ "@opengeni/github": "^0.6.1",
58
+ "@opengeni/network": "^0.2.3",
59
+ "@opengeni/observability": "^0.8.9",
60
+ "@opengeni/runtime": "^1.4.2",
61
+ "@opengeni/storage": "^0.2.110",
62
+ "@opengeni/xai-subscription": "^0.1.2",
63
63
  "@temporalio/client": "^1.17.0",
64
64
  "better-auth": "^1.6.14",
65
65
  "hono": "^4.12.18",
package/src/app.ts CHANGED
@@ -36,7 +36,10 @@ import {
36
36
  configureChildLifecycleNotices,
37
37
  configureWorkspaceControlRequestLockTimeoutMs,
38
38
  dbSql,
39
+ getManagedAuthSessionSetSnapshot,
39
40
  getWorkspace,
41
+ reapManagedAuthIsolatedSessions,
42
+ reapExpiredManagedAuthSessionSets,
40
43
  rlsContextForWorkspace,
41
44
  withSessionRlsActorContext,
42
45
  } from "@opengeni/db";
@@ -49,21 +52,32 @@ import { Hono } from "hono";
49
52
  import { bodyLimit } from "hono/body-limit";
50
53
  import { compress } from "hono/compress";
51
54
  import { cors } from "hono/cors";
55
+ import { getCookie } from "hono/cookie";
52
56
  import { HTTPException } from "hono/http-exception";
53
57
  import { ApiHttpError, workspaceControlBusyHttpError } from "./http/api-error";
54
58
  import type { ContentfulStatusCode } from "hono/utils/http-status";
55
59
  import type { ApiRouteDeps, AppDependencies } from "@opengeni/core";
56
60
  import {
57
61
  CodexCompactionV2ProviderLockedError,
62
+ ManagedAuthActorLeaseOutcomeUnknownError,
58
63
  hasPermission,
59
64
  requireAccessGrant,
60
65
  requireLiveAgentAttemptAuthorization,
61
66
  requirePermission,
67
+ releaseManagedAuthRequestActorLease,
68
+ validateManagedAuthRequestActorLease,
62
69
  requireSessionAuthorization,
63
70
  SessionAuthorizationDeniedError,
64
71
  SessionAuthorizationUnavailableError,
65
72
  } from "@opengeni/core";
66
73
  import { createManagedAuth } from "./auth/managed-auth";
74
+ import {
75
+ MANAGED_AUTH_SESSION_SET_COOKIE,
76
+ ManagedAuthActorChangeError,
77
+ managedAuthSha256,
78
+ } from "@opengeni/core/managed-auth-session-sets";
79
+ import { createBetterAuthSessionAdapter } from "./auth/managed-auth-session-adapter";
80
+ import { runManagedAuthDiscardedProviderSession } from "./auth/managed-auth-attempt-context";
67
81
  import { createManagedEmailTransport } from "./auth/managed-email";
68
82
  import { assertManagedEmailTransportMetadata } from "./auth/organization-user-setup";
69
83
  import { createApiSandboxClient, makeResumeBoxById } from "./sandbox/access";
@@ -135,7 +149,13 @@ import { registerEditableArtifactRoutes } from "./routes/editable-artifacts";
135
149
  import { registerVideoGenerationRoutes } from "./routes/video-generation";
136
150
  import { registerCanonicalHumanIdentityRoutes } from "./routes/canonical-human-identities";
137
151
  import { registerOrganizationMembershipRoutes } from "./routes/organization-memberships";
152
+ import { registerOrganizationRecoveryRoutes } from "./routes/organization-recovery";
138
153
  import { registerManagedOnboardingRoutes } from "./routes/managed-onboarding";
154
+ import {
155
+ registerManagedAuthSessionSetRoutes,
156
+ requireManagedAuthProviderRouteAllowed,
157
+ scrubManagedAuthProviderResponse,
158
+ } from "./routes/managed-auth-session-sets";
139
159
  import { registerUserResourceAuthorityRoutes } from "./routes/user-resource-authorities";
140
160
  import { registerConnectionAuthorityRoutes } from "./routes/connection-authorities";
141
161
  import { projectClientModel } from "./model-catalog";
@@ -167,6 +187,8 @@ export { workflowIdForSession } from "@opengeni/core";
167
187
  export { replaySessionEvents, sseSessionStream, sseWorkspaceControlStream } from "./http/sse";
168
188
 
169
189
  export const API_MAX_REQUEST_BODY_BYTES = 32 * 1024 * 1024;
190
+ const managedAuthReaperDatabases = new WeakSet<object>();
191
+ const MANAGED_AUTH_REAPER_INTERVAL_MS = 60_000;
170
192
 
171
193
  /** Effective Hono bodyLimit — API JSON ceiling or voice multipart + multipart overhead. */
172
194
  export function apiRequestBodyLimitBytes(settings: {
@@ -202,6 +224,9 @@ export function createAppComposition(deps: AppDependencies): {
202
224
  assertManagedEmailTransportMetadata(managedEmailTransport);
203
225
  const managedAuth =
204
226
  deps.managedAuth ?? createManagedAuth(deps.settings, deps.db, managedEmailTransport);
227
+ const managedAuthSessionAdapter =
228
+ deps.managedAuthSessionAdapter ??
229
+ (managedAuth ? createBetterAuthSessionAdapter(managedAuth, deps.db) : null);
205
230
  const objectStorage =
206
231
  deps.objectStorage === undefined ? createObjectStorage(deps.settings) : deps.objectStorage;
207
232
  let documentServices: DocumentServices | null = deps.documentServices ?? null;
@@ -284,6 +309,24 @@ export function createAppComposition(deps: AppDependencies): {
284
309
  const resumeBoxById = deps.resumeBoxById ?? makeResumeBoxById(sandboxClient);
285
310
  const observability =
286
311
  deps.observability ?? createObservability(deps.settings, { component: "api" });
312
+ if (
313
+ managedAuth &&
314
+ deps.settings.managedAuthSessionSetMode !== "legacy" &&
315
+ !managedAuthReaperDatabases.has(deps.db as object)
316
+ ) {
317
+ managedAuthReaperDatabases.add(deps.db as object);
318
+ const timer = setInterval(() => {
319
+ void Promise.all([
320
+ reapManagedAuthIsolatedSessions(deps.db, 100),
321
+ reapExpiredManagedAuthSessionSets(deps.db, 100),
322
+ ]).catch((error) => {
323
+ observability.error("Managed isolated-auth orphan reap failed", {
324
+ errorClass: error instanceof Error ? error.name : "UnknownError",
325
+ });
326
+ });
327
+ }, MANAGED_AUTH_REAPER_INTERVAL_MS);
328
+ (timer as ReturnType<typeof setInterval> & { unref?: () => void }).unref?.();
329
+ }
287
330
  const transcription =
288
331
  deps.transcription === undefined
289
332
  ? createTranscriptionService({
@@ -304,6 +347,7 @@ export function createAppComposition(deps: AppDependencies): {
304
347
  githubStateSecret:
305
348
  deps.githubStateSecret ?? deps.settings.githubAppManifestStateSecret ?? crypto.randomUUID(),
306
349
  managedAuth,
350
+ managedAuthSessionAdapter,
307
351
  managedEmailTransport,
308
352
  objectStorage,
309
353
  documentIndexer,
@@ -332,13 +376,17 @@ export function createAppComposition(deps: AppDependencies): {
332
376
  "Range",
333
377
  "X-OpenGeni-Access-Key",
334
378
  "X-OpenGeni-Api-Contract",
379
+ "X-OpenGeni-Actor-Epoch",
335
380
  "X-OpenGeni-Correlation-Id",
381
+ "X-OpenGeni-Session-Csrf",
336
382
  "X-OpenGeni-Subject",
337
383
  ],
338
384
  exposeHeaders: [
339
385
  "Accept-Ranges",
340
386
  "Content-Range",
341
387
  "X-OpenGeni-Api-Contract",
388
+ "X-OpenGeni-Actor-Epoch",
389
+ "X-OpenGeni-Actor-State",
342
390
  "X-OpenGeni-Correlation-Id",
343
391
  ],
344
392
  };
@@ -495,11 +543,93 @@ export function createAppComposition(deps: AppDependencies): {
495
543
  await next();
496
544
  });
497
545
 
546
+ app.use("/v1/*", async (c, next) => {
547
+ try {
548
+ await next();
549
+ try {
550
+ await validateManagedAuthRequestActorLease(c.req.raw);
551
+ } catch (error) {
552
+ if (error instanceof ManagedAuthActorChangeError) {
553
+ c.header("x-opengeni-actor-state", "changed");
554
+ throw new HTTPException(409, { message: error.code, cause: error });
555
+ }
556
+ if (error instanceof ManagedAuthActorLeaseOutcomeUnknownError) {
557
+ throw new ApiHttpError(503, {
558
+ code: "upstream_unavailable",
559
+ message: error.code,
560
+ retryable: true,
561
+ outcomeUnknown: true,
562
+ details: { managedAuthCode: error.code },
563
+ });
564
+ }
565
+ throw error;
566
+ }
567
+ } finally {
568
+ await releaseManagedAuthRequestActorLease(c.req.raw).catch((error) => {
569
+ observability.error("Managed actor mutation lease release failed", {
570
+ errorClass: error instanceof Error ? error.name : "UnknownError",
571
+ });
572
+ });
573
+ }
574
+ });
575
+
498
576
  // These product-owned auth routes must be registered before Better Auth's
499
577
  // wildcard handler or the provider returns its own 404 first.
500
578
  registerManagedOnboardingRoutes(app, routeDeps);
579
+ registerManagedAuthSessionSetRoutes(app, routeDeps);
501
580
  if (managedAuth) {
502
- app.on(["GET", "POST"], "/v1/auth/*", (c) => managedAuth.handler(c.req.raw));
581
+ app.on(["GET", "POST"], "/v1/auth/*", async (c) => {
582
+ if (deps.settings.managedAuthSessionSetMode === "legacy") {
583
+ return await managedAuth.handler(c.req.raw);
584
+ }
585
+ requireManagedAuthProviderRouteAllowed(c.req.method, new URL(c.req.url).pathname);
586
+ // Provider authentication/recovery is isolated from whichever actor the
587
+ // browser currently renders. Selected-session capabilities are all
588
+ // 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
+ const authority = getCookie(c, MANAGED_AUTH_SESSION_SET_COOKIE);
595
+ const discardProviderSession =
596
+ deps.settings.managedAuthSessionSetMode === "broker" || authority !== undefined;
597
+ const providerResponse = discardProviderSession
598
+ ? await runManagedAuthDiscardedProviderSession(
599
+ async () => await managedAuth.handler(providerRequest),
600
+ )
601
+ : await managedAuth.handler(providerRequest);
602
+ let replacementCookies: readonly string[] | undefined;
603
+ if (deps.settings.managedAuthSessionSetMode === "broker") {
604
+ replacementCookies = await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
605
+ null,
606
+ c.req.header("cookie") ?? null,
607
+ );
608
+ } else {
609
+ if (authority !== undefined) {
610
+ const snapshot = await getManagedAuthSessionSetSnapshot(deps.db, {
611
+ authorityHash: managedAuthSha256(authority),
612
+ mode: "dual",
613
+ includeInternal: true,
614
+ readOnly: true,
615
+ });
616
+ if (snapshot) {
617
+ replacementCookies =
618
+ await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
619
+ snapshot.selected,
620
+ c.req.header("cookie") ?? null,
621
+ );
622
+ } else {
623
+ replacementCookies =
624
+ await managedAuthSessionAdapter!.createLegacySelectedSessionCookies(
625
+ null,
626
+ c.req.header("cookie") ?? null,
627
+ );
628
+ }
629
+ }
630
+ }
631
+ return await scrubManagedAuthProviderResponse(providerResponse, { replacementCookies });
632
+ });
503
633
  }
504
634
 
505
635
  app.get("/healthz", (c) =>
@@ -585,6 +715,7 @@ export function createAppComposition(deps: AppDependencies): {
585
715
  : {}),
586
716
  },
587
717
  productAccessMode: deps.settings.productAccessMode,
718
+ managedAuthSessionSetMode: deps.settings.managedAuthSessionSetMode,
588
719
  auth: clientAuthConfig(deps.settings),
589
720
  analytics: clientAnalyticsConfig(deps.settings),
590
721
  // Channel-A structured services (P4.4) ride exec/readFile/createEditor,
@@ -765,6 +896,7 @@ export function createAppComposition(deps: AppDependencies): {
765
896
  registerVideoGenerationRoutes(app, routeDeps);
766
897
  registerCanonicalHumanIdentityRoutes(app, routeDeps);
767
898
  registerOrganizationMembershipRoutes(app, routeDeps);
899
+ registerOrganizationRecoveryRoutes(app, routeDeps);
768
900
  registerUserResourceAuthorityRoutes(app, routeDeps);
769
901
  registerConnectionAuthorityRoutes(app, routeDeps);
770
902
  registerSlackInteractionRoutes(app, routeDeps);
@@ -0,0 +1,24 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ type ManagedAuthAttemptContext =
4
+ | { kind: "isolated_transaction"; transactionId: string }
5
+ | { kind: "discard_provider_session" };
6
+
7
+ const managedAuthAttemptStorage = new AsyncLocalStorage<ManagedAuthAttemptContext>();
8
+
9
+ export function runManagedAuthAttempt<T>(transactionId: string, action: () => T): T {
10
+ return managedAuthAttemptStorage.run({ kind: "isolated_transaction", transactionId }, action);
11
+ }
12
+
13
+ export function runManagedAuthDiscardedProviderSession<T>(action: () => T): T {
14
+ return managedAuthAttemptStorage.run({ kind: "discard_provider_session" }, action);
15
+ }
16
+
17
+ export function currentManagedAuthAttemptId(): string | null {
18
+ const context = managedAuthAttemptStorage.getStore();
19
+ return context?.kind === "isolated_transaction" ? context.transactionId : null;
20
+ }
21
+
22
+ export function shouldDiscardCurrentManagedAuthProviderSession(): boolean {
23
+ return managedAuthAttemptStorage.getStore()?.kind === "discard_provider_session";
24
+ }