@opengeni/api-router 4.1.0 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createOpenGeniSlackBotInteractionClient,
8
8
  startSlackInteractionPump,
9
9
  startTemporalScheduleCleanupPump
10
- } from "./chunk-24C5LPNE.js";
10
+ } from "./chunk-2SSU5KWV.js";
11
11
 
12
12
  // src/index.ts
13
13
  import { scheduledTaskKnowledgeSource } from "@opengeni/contracts";
@@ -46,7 +46,7 @@ export declare function requireGitHubLensConnect(deps: ApiRouteDeps): Promise<{
46
46
  eager?: boolean | undefined;
47
47
  optional?: boolean | undefined;
48
48
  }[];
49
- firstPartyMcpTools: ("artifacts_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "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" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "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_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "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_set_project" | "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" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
49
+ firstPartyMcpTools: ("artifacts_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "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_read" | "browser_screenshot" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "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_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "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_set_project" | "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" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
50
50
  firstPartyMcpPermissions: ("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")[];
51
51
  model: string | null;
52
52
  reasoningEffort: "high" | "low" | "max" | "medium" | "minimal" | "none" | "xhigh" | null;
@@ -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_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "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" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "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_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "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_set_project" | "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" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "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_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "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_read" | "browser_screenshot" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "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_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "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_set_project" | "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" | "wait_for_input" | "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;
@@ -1,3 +1,4 @@
1
+ import type { Settings } from "@opengeni/config";
1
2
  import type { DocumentServices } from "@opengeni/documents";
2
3
  import { type KnowledgeContext, type Database } from "@opengeni/db";
3
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -5,4 +6,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  * chunk/base retrieval is retired: it cannot bypass pending Knowledge review. */
6
7
  export declare function buildDocumentsMcpServer(db: Database, accountId: string, workspaceId: string, documentServices: DocumentServices, options: {
7
8
  knowledge: KnowledgeContext;
9
+ settings?: Settings;
8
10
  }): McpServer;
@@ -271,6 +271,14 @@ export declare const FIRST_PARTY_TOOL_AUTHORIZATION: {
271
271
  sessionRequired: true;
272
272
  allOf: "sessions:read"[];
273
273
  };
274
+ browser_read: {
275
+ sessionRequired: true;
276
+ allOf: "sessions:read"[];
277
+ };
278
+ browser_screenshot: {
279
+ sessionRequired: true;
280
+ allOf: "sessions:read"[];
281
+ };
274
282
  browser_act: {
275
283
  sessionRequired: true;
276
284
  allOf: "sessions:control"[];
@@ -1,5 +1,6 @@
1
1
  import { InteractionActor, type AccessGrant, type FileAsset } from "@opengeni/contracts";
2
2
  import { type ApiRouteDeps, type ResolvedSessionAuthorization } from "@opengeni/core";
3
+ import { type BrowserControlScreenshotOptions, type BrowserControlFrame } from "@opengeni/runtime/sandbox";
3
4
  import type { Hono } from "hono";
4
5
  export declare function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): void;
5
6
  export declare function browserNeedsStandaloneDisplayStack(input: {
@@ -7,6 +8,8 @@ export declare function browserNeedsStandaloneDisplayStack(input: {
7
8
  linkedComputer: boolean;
8
9
  nativeBrowserControl: boolean;
9
10
  }): boolean;
11
+ export declare function parseBrowserScreenshotOptions(query: URLSearchParams): BrowserControlScreenshotOptions;
12
+ export declare function browserScreenshotResponse(frame: Pick<BrowserControlFrame, "data" | "mediaType" | "metadataHeader">): Response;
10
13
  export declare function interactionActorForGrant(grant: AccessGrant): ReturnType<typeof InteractionActor.parse>;
11
14
  /** Resolve Drive authority from the same immutable session authorization that
12
15
  * admitted the browser action. Agent-attempt subjects are technical worker
@@ -0,0 +1,5 @@
1
+ import type { GitHubInstallationBindingCandidate } from "@opengeni/contracts";
2
+ export declare function githubInstallationChooserHtml(candidates: GitHubInstallationBindingCandidate[], state: string, workspaceId: string, baseUrl: string): string;
3
+ export declare function githubSetupSuccessHtml(account: string, returnUrl: string): string;
4
+ export declare function githubSetupPendingHtml(): string;
5
+ export declare function githubSuccessHtml(envLines: string[]): string;
@@ -1,4 +1,30 @@
1
1
  import { type ApiRouteDeps, type WorkspaceInsightsFilterField } from "@opengeni/core";
2
2
  import type { Hono } from "hono";
3
3
  export declare function normalizeWorkspaceInsightsQueryFilter(value: string | null | undefined, field: WorkspaceInsightsFilterField): string | null;
4
+ /**
5
+ * Share one in-flight computation between identical concurrent callers.
6
+ *
7
+ * A reload, a second tab, or a retried fetch joins the rollup already running
8
+ * instead of starting another multi-second aggregate on the database.
9
+ * Authorization and filter validation still run per request before a caller
10
+ * may join; the map only ever holds running work. The caller supplies the
11
+ * complete sharing key, which must identify everything the result depends on.
12
+ */
13
+ export declare function createInFlightCoalescer<T>(): {
14
+ run: (key: string, work: () => Promise<T>) => Promise<T>;
15
+ readonly size: number;
16
+ };
17
+ /**
18
+ * Insights rows are filtered by the database RLS actor (private sessions are
19
+ * visible only to their owner), so two administrators of one workspace can
20
+ * legitimately receive different responses. The actor identity is part of
21
+ * the key; requests share work only when every visibility input is equal.
22
+ */
23
+ export declare function workspaceInsightsCoalesceKey(input: {
24
+ workspaceId: string;
25
+ range: string;
26
+ provider: string | null;
27
+ model: string | null;
28
+ rlsActor: string | null;
29
+ }): string;
4
30
  export declare function registerInsightsRoutes(app: Hono, deps: ApiRouteDeps): void;
@@ -1,6 +1,7 @@
1
1
  import { SandboxFileArtifactReceipt, type AccessGrant, type Session } from "@opengeni/contracts";
2
2
  import { type ApiRouteDeps, type AccessGrantAuthorization } from "@opengeni/core";
3
3
  import { getFile } from "@opengeni/db";
4
+ import { type SandboxChannelAService } from "@opengeni/runtime/sandbox";
4
5
  /** Original bytes inherit the already-authorized source session's ownership. */
5
6
  export declare function publishSandboxFileArtifact(deps: ApiRouteDeps, input: Parameters<typeof publishSandboxFileArtifactInScope>[1]): Promise<SandboxFileArtifactReceipt>;
6
7
  declare function publishSandboxFileArtifactInScope(deps: ApiRouteDeps, input: {
@@ -10,7 +11,22 @@ declare function publishSandboxFileArtifactInScope(deps: ApiRouteDeps, input: {
10
11
  path: string;
11
12
  signal?: AbortSignal | undefined;
12
13
  }): Promise<SandboxFileArtifactReceipt>;
13
- export declare function sandboxArtifactRelativePath(value: string): string;
14
+ /** Use the root captured by the same fenced Channel-A service that reads bytes,
15
+ * not the session's placement-home label or a caller-supplied root. */
16
+ export declare function readSandboxArtifactFile(service: Pick<SandboxChannelAService, "capabilities" | "fsRead">, value: string, maxBytes: number): Promise<{
17
+ path: string;
18
+ sandboxPath: string;
19
+ read: {
20
+ path: string;
21
+ encoding: "base64" | "utf8";
22
+ content: string;
23
+ sizeBytes: number;
24
+ truncated: boolean;
25
+ isBinary: boolean;
26
+ revision: number;
27
+ };
28
+ }>;
29
+ export declare function sandboxArtifactRelativePath(value: string, workspaceRoot?: string): string;
14
30
  export declare function sandboxArtifactSafeFilename(filename: string): string;
15
31
  export declare function sandboxFileContentType(filename: string): string;
16
32
  export declare function sandboxArtifactIdentity(input: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
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": {
@@ -44,22 +44,22 @@
44
44
  "@llamaindex/liteparse": "2.14.2",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
46
  "@opengeni/agent-proto": "^0.6.0",
47
- "@opengeni/artifact-tool": "^0.3.31",
47
+ "@opengeni/artifact-tool": "^0.3.32",
48
48
  "@opengeni/capabilities": "^0.3.4",
49
- "@opengeni/codemode": "^0.6.1",
50
- "@opengeni/codex": "^0.2.25",
51
- "@opengeni/config": "^2.1.0",
52
- "@opengeni/contracts": "^5.1.0",
53
- "@opengeni/core": "^4.0.1",
54
- "@opengeni/db": "^6.0.1",
55
- "@opengeni/documents": "^0.8.32",
56
- "@opengeni/events": "^0.4.30",
57
- "@opengeni/github": "^0.7.15",
49
+ "@opengeni/codemode": "^0.6.2",
50
+ "@opengeni/codex": "^0.2.26",
51
+ "@opengeni/config": "^2.1.1",
52
+ "@opengeni/contracts": "^5.1.1",
53
+ "@opengeni/core": "^4.0.3",
54
+ "@opengeni/db": "^6.0.3",
55
+ "@opengeni/documents": "^0.8.34",
56
+ "@opengeni/events": "^0.4.32",
57
+ "@opengeni/github": "^0.7.16",
58
58
  "@opengeni/network": "^0.3.1",
59
- "@opengeni/observability": "^0.8.31",
60
- "@opengeni/runtime": "^4.0.1",
61
- "@opengeni/storage": "^0.2.132",
62
- "@opengeni/tool-gateway": "^0.1.12",
59
+ "@opengeni/observability": "^0.8.32",
60
+ "@opengeni/runtime": "^4.0.3",
61
+ "@opengeni/storage": "^0.2.133",
62
+ "@opengeni/tool-gateway": "^0.1.13",
63
63
  "@opengeni/xai-subscription": "^0.1.4",
64
64
  "@temporalio/client": "^1.17.0",
65
65
  "better-auth": "^1.6.14",
@@ -1,4 +1,5 @@
1
1
  import { searchKnowledgeEntries } from "@opengeni/core";
2
+ import type { Settings } from "@opengeni/config";
2
3
  import { KnowledgeEntryListRequest } from "@opengeni/contracts";
3
4
  import type { DocumentServices } from "@opengeni/documents";
4
5
  import {
@@ -17,7 +18,7 @@ export function buildDocumentsMcpServer(
17
18
  accountId: string,
18
19
  workspaceId: string,
19
20
  documentServices: DocumentServices,
20
- options: { knowledge: KnowledgeContext },
21
+ options: { knowledge: KnowledgeContext; settings?: Settings },
21
22
  ): McpServer {
22
23
  const context = options.knowledge;
23
24
  if (context.accountId !== accountId || context.workspaceId !== workspaceId)
@@ -39,7 +40,13 @@ export function buildDocumentsMcpServer(
39
40
  {
40
41
  type: "text",
41
42
  text: JSON.stringify(
42
- await searchKnowledgeEntries(db, context, input, () => documentServices.embedder),
43
+ await searchKnowledgeEntries(
44
+ db,
45
+ context,
46
+ input,
47
+ () => documentServices.embedder,
48
+ options.settings,
49
+ ),
43
50
  ),
44
51
  },
45
52
  ],
@@ -117,6 +117,8 @@ export const FIRST_PARTY_TOOL_AUTHORIZATION = {
117
117
  browser_open: { sessionRequired: true, allOf: ["sessions:control"] },
118
118
  browser_tabs: { sessionRequired: true, allOf: ["sessions:control"] },
119
119
  browser_observe: { sessionRequired: true, allOf: ["sessions:read"] },
120
+ browser_read: { sessionRequired: true, allOf: ["sessions:read"] },
121
+ browser_screenshot: { sessionRequired: true, allOf: ["sessions:read"] },
120
122
  browser_act: { sessionRequired: true, allOf: ["sessions:control"] },
121
123
  browser_clipboard: { sessionRequired: true, allOf: ["sessions:read"] },
122
124
  browser_debug: { sessionRequired: true, allOf: ["sessions:read"] },
@@ -142,7 +142,14 @@ export function registerKnowledgeEntryTools(
142
142
  },
143
143
  (input) =>
144
144
  run((context) =>
145
- prepareKnowledgeSave(deps.db, context, input, () => deps.getDocumentServices().embedder),
145
+ prepareKnowledgeSave(
146
+ deps.db,
147
+ context,
148
+ input,
149
+ () => deps.getDocumentServices().embedder,
150
+ undefined,
151
+ deps.settings,
152
+ ),
146
153
  ),
147
154
  );
148
155
  server.registerTool(
@@ -159,7 +166,13 @@ export function registerKnowledgeEntryTools(
159
166
  },
160
167
  (input) =>
161
168
  run((context) =>
162
- searchKnowledgeEntries(deps.db, context, input, () => deps.getDocumentServices().embedder),
169
+ searchKnowledgeEntries(
170
+ deps.db,
171
+ context,
172
+ input,
173
+ () => deps.getDocumentServices().embedder,
174
+ deps.settings,
175
+ ),
163
176
  ),
164
177
  );
165
178
  server.registerTool(
package/src/mcp/server.ts CHANGED
@@ -5854,9 +5854,14 @@ function registerCapabilityDiscoveryTools(
5854
5854
  // This is a readiness projection, never credential resolution or a new grant.
5855
5855
  const setupProjections = async (items: CapabilityCatalogItem[]) => {
5856
5856
  const availableServerIds = new Set<string>();
5857
+ const availableFirstPartyTools = new Set<string>();
5857
5858
  const acceptedServerIds = new Set<string>();
5858
5859
  let accountSelectionKnown = false;
5859
- if (items.some((item) => item.enabled && item.connectionRef)) {
5860
+ if (
5861
+ items.some(
5862
+ (item) => item.enabled && (item.connectionRef || item.surfaceType === "first_party_fiken"),
5863
+ )
5864
+ ) {
5860
5865
  const claims = exactAgentCommandContext(grant, sessionId);
5861
5866
  const attemptCatalog = await getAttemptToolCatalog(deps.db, {
5862
5867
  accountId: grant.accountId,
@@ -5868,7 +5873,11 @@ function registerCapabilityDiscoveryTools(
5868
5873
  attemptCatalog.turnId === claims.callerTurnId &&
5869
5874
  attemptCatalog.executionGeneration === claims.callerExecutionGeneration
5870
5875
  ) {
5871
- for (const entry of attemptCatalog.entries) availableServerIds.add(entry.identity.serverId);
5876
+ for (const entry of attemptCatalog.entries) {
5877
+ availableServerIds.add(entry.identity.serverId);
5878
+ if (entry.identity.serverId === "opengeni")
5879
+ availableFirstPartyTools.add(entry.identity.toolName);
5880
+ }
5872
5881
  const bindings = await getSessionTurnMcpAccountBindings(
5873
5882
  deps.db,
5874
5883
  grant.workspaceId,
@@ -5890,6 +5899,8 @@ function registerCapabilityDiscoveryTools(
5890
5899
  availableServerIds,
5891
5900
  acceptedServerIds,
5892
5901
  accountSelectionKnown,
5902
+ availableFirstPartyTools,
5903
+ new Set((grant.metadata?.firstPartyMcpTools as string[] | undefined) ?? []),
5893
5904
  ),
5894
5905
  ),
5895
5906
  );
@@ -6024,7 +6035,45 @@ async function capabilitySetupProjection(
6024
6035
  availableServerIds: ReadonlySet<string>,
6025
6036
  acceptedServerIds: ReadonlySet<string>,
6026
6037
  accountSelectionKnown: boolean,
6038
+ availableFirstPartyTools: ReadonlySet<string>,
6039
+ selectedFirstPartyTools: ReadonlySet<string>,
6027
6040
  ): Promise<CapabilitySetupProjection> {
6041
+ if (item.surfaceType === "first_party_fiken") {
6042
+ if (!item.enabled || item.metadata.connectionStatus !== "active") {
6043
+ return {
6044
+ status: "authorization_required",
6045
+ action: "connect",
6046
+ detail:
6047
+ item.metadata.connectionStatus === "needs_reauth"
6048
+ ? "The workspace Fiken connection needs reconnection."
6049
+ : "Connect Fiken through the protected setup form.",
6050
+ };
6051
+ }
6052
+ const selected = FIRST_PARTY_MCP_TOOL_NAMES.filter(
6053
+ (name) => name.startsWith("fiken_") && selectedFirstPartyTools.has(name),
6054
+ );
6055
+ if (selected.length === 0) {
6056
+ return {
6057
+ status: "authorization_required",
6058
+ action: "enable",
6059
+ detail:
6060
+ "Fiken is connected, but its tools are not selected for this conversation. Ask the user to add its tools through the setup card, then send a new message.",
6061
+ };
6062
+ }
6063
+ if (selected.some((name) => availableFirstPartyTools.has(name))) {
6064
+ return {
6065
+ status: "ready",
6066
+ action: null,
6067
+ detail: "Fiken is connected and has tools available in this turn.",
6068
+ };
6069
+ }
6070
+ return {
6071
+ status: "unavailable",
6072
+ action: null,
6073
+ detail:
6074
+ "Fiken is connected, but its selected tools are unavailable in this execution. Check tool permissions and setup before retrying; reconnection is not required by this status.",
6075
+ };
6076
+ }
6028
6077
  if (item.id === "api:github-app" || item.surfaceType === "first_party_github") {
6029
6078
  const missing = githubAppMissingSettings(deps.settings);
6030
6079
  if (missing.length > 0) {
@@ -3,6 +3,7 @@ import {
3
3
  environmentsEncryptionKeyBytes,
4
4
  resolveFirstPartyDelegationSecret,
5
5
  resolveStreamTokenSecret,
6
+ sandboxWarmRateMicrosPerSecond,
6
7
  } from "@opengeni/config";
7
8
  import {
8
9
  BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX,
@@ -20,6 +21,8 @@ import {
20
21
  BrowserDownloadSaveRequest,
21
22
  BrowserDownloadSaveResponse,
22
23
  BrowserDownloadListResponse,
24
+ BrowserDomReadRequest,
25
+ BrowserDomReadResponse,
23
26
  BrowserExternalAuthCommand,
24
27
  BrowserOpenTargetRequest,
25
28
  BrowserObservation,
@@ -31,6 +34,7 @@ import {
31
34
  BrowserSessionListResponse,
32
35
  BrowserSessionMutationResponse,
33
36
  BrowserTargetListResponse,
37
+ BrowserTargetState,
34
38
  CreateBrowserSessionRequest,
35
39
  ExternalAuthInteractiveRequest,
36
40
  ExternalAuthInteractiveResponse,
@@ -75,6 +79,7 @@ import {
75
79
  BrowserSessionNotFoundError,
76
80
  BrowserSessionOperationConflictError,
77
81
  BrowserSessionStateError,
82
+ SandboxPaidComputeAdmissionError,
78
83
  completeBrowserSessionEnd,
79
84
  completeExternalAuth,
80
85
  completeBrowserDownloadSave,
@@ -88,6 +93,7 @@ import {
88
93
  dispatchExternalAuth,
89
94
  dispatchProtectedAuthFill,
90
95
  failBrowserSessionOperation,
96
+ failPreparedBrowserSessionSuspend,
91
97
  failBrowserSessionResume,
92
98
  failBrowserSessionResumePreparation,
93
99
  failBrowserSessionSuspension,
@@ -169,6 +175,8 @@ import {
169
175
  provisionBrowserControlClient,
170
176
  renewSandboxProviderExpiration,
171
177
  type BrowserControlPlacementSession,
178
+ type BrowserControlScreenshotOptions,
179
+ type BrowserControlFrame,
172
180
  type PlacementBrowserStateCaptureReceipt,
173
181
  type PlacementBrowserNetworkRoute,
174
182
  type PlacementBrowserTransport,
@@ -952,6 +960,71 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
952
960
  },
953
961
  );
954
962
 
963
+ app.get(
964
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/state",
965
+ async (context) => {
966
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
967
+ context,
968
+ "sessions:read",
969
+ );
970
+ const targetId = requireOpaqueParam(context, "targetId");
971
+ const result = await withActiveBrowserController(
972
+ context,
973
+ grant,
974
+ workspaceId,
975
+ browserSessionId,
976
+ "session.read",
977
+ "browser.read",
978
+ async ({ sessionClient }) => await sessionClient.targetState(targetId),
979
+ );
980
+ return context.json(BrowserTargetState.parse(result));
981
+ },
982
+ );
983
+
984
+ app.post(
985
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/dom-read",
986
+ async (context) => {
987
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
988
+ context,
989
+ "sessions:read",
990
+ );
991
+ const targetId = requireOpaqueParam(context, "targetId");
992
+ const request = await parseJsonBody(context, BrowserDomReadRequest);
993
+ const result = await withActiveBrowserController(
994
+ context,
995
+ grant,
996
+ workspaceId,
997
+ browserSessionId,
998
+ "session.read",
999
+ "browser.read",
1000
+ async ({ sessionClient }) => await sessionClient.readDom(targetId, request),
1001
+ );
1002
+ return context.json(BrowserDomReadResponse.parse(result));
1003
+ },
1004
+ );
1005
+
1006
+ app.get(
1007
+ "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/screenshot",
1008
+ async (context) => {
1009
+ const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
1010
+ context,
1011
+ "sessions:read",
1012
+ );
1013
+ const targetId = requireOpaqueParam(context, "targetId");
1014
+ const captureOptions = parseBrowserScreenshotOptions(new URL(context.req.url).searchParams);
1015
+ const frame = await withActiveBrowserController(
1016
+ context,
1017
+ grant,
1018
+ workspaceId,
1019
+ browserSessionId,
1020
+ "session.read",
1021
+ "browser.read",
1022
+ async ({ sessionClient }) => await sessionClient.capture(targetId, captureOptions),
1023
+ );
1024
+ return browserScreenshotResponse(frame);
1025
+ },
1026
+ );
1027
+
955
1028
  app.post(
956
1029
  "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/actions",
957
1030
  async (context) => {
@@ -2047,6 +2120,7 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
2047
2120
  const request = await parseJsonBody(context, BrowserSessionLifecycleRequest);
2048
2121
  const startedAtMs = performance.now();
2049
2122
  const origin = requestOrigin(context, deps.settings);
2123
+ let preparedForDispatch = false;
2050
2124
  try {
2051
2125
  const before = await getBrowserSessionControlRecord(deps.db, {
2052
2126
  accountId: grant.accountId,
@@ -2098,6 +2172,8 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
2098
2172
  return context.json(parsed, 200);
2099
2173
  }
2100
2174
 
2175
+ preparedForDispatch = true;
2176
+
2101
2177
  const record = await getBrowserSessionControlRecord(deps.db, {
2102
2178
  accountId: grant.accountId,
2103
2179
  workspaceId,
@@ -2259,6 +2335,15 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
2259
2335
  observeLifecycleResult(deps.observability, startedAtMs, parsed);
2260
2336
  return context.json(parsed, 200);
2261
2337
  } catch (error) {
2338
+ if (preparedForDispatch) {
2339
+ await failPreparedBrowserSessionSuspend(deps.db, {
2340
+ accountId: grant.accountId,
2341
+ workspaceId,
2342
+ browserSessionId,
2343
+ operationId: request.operationId,
2344
+ error: interactionFailure(error),
2345
+ });
2346
+ }
2262
2347
  throw browserRouteError(error);
2263
2348
  }
2264
2349
  },
@@ -2792,6 +2877,12 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
2792
2877
  resolved.kind !== "selfhosted" ||
2793
2878
  resolved.sandboxId !== expectedPlacement.sandboxId
2794
2879
  ) {
2880
+ if (operation === "browser.create") {
2881
+ throw new HTTPException(422, {
2882
+ message:
2883
+ "The requested Connected Machine is not this session's current placement. Move the session to that machine before creating an interaction resource.",
2884
+ });
2885
+ }
2795
2886
  return await throwBrowserSourcePlacementChanged(
2796
2887
  grant,
2797
2888
  sourceSession.id,
@@ -3748,6 +3839,10 @@ async function ensureInteractionHolder(
3748
3839
  holderId: interactionHolderId(browserSessionId),
3749
3840
  subjectId: sourceSession.id,
3750
3841
  backend: placement.lease.backend,
3842
+ warmBilling: {
3843
+ mode: deps.settings.sandboxWarmBillingMode,
3844
+ rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond(deps.settings, placement.lease.backend),
3845
+ },
3751
3846
  os: placement.lease.os,
3752
3847
  image: sandboxRuntime.image,
3753
3848
  rigVersionId: sourceSession.rigVersionId,
@@ -4160,6 +4255,49 @@ function optionalBoundedInteger(
4160
4255
  return parsed;
4161
4256
  }
4162
4257
 
4258
+ export function parseBrowserScreenshotOptions(
4259
+ query: URLSearchParams,
4260
+ ): BrowserControlScreenshotOptions {
4261
+ for (const key of ["fullPage", "format", "quality"]) {
4262
+ if (query.getAll(key).length > 1) {
4263
+ throw new HTTPException(400, { message: `duplicate browser screenshot ${key}` });
4264
+ }
4265
+ }
4266
+ const fullPage = query.get("fullPage");
4267
+ if (fullPage !== null && fullPage !== "true" && fullPage !== "false") {
4268
+ throw new HTTPException(400, { message: "invalid browser screenshot fullPage" });
4269
+ }
4270
+ const format = query.get("format");
4271
+ if (format !== null && format !== "jpeg" && format !== "png") {
4272
+ throw new HTTPException(400, { message: "invalid browser screenshot format" });
4273
+ }
4274
+ const quality = query.get("quality");
4275
+ const parsedQuality = quality === null ? null : optionalBoundedInteger(quality, 1, 100);
4276
+ if (quality !== null && parsedQuality === null) {
4277
+ throw new HTTPException(400, { message: "invalid browser screenshot quality" });
4278
+ }
4279
+ return {
4280
+ ...(fullPage === null ? {} : { fullPage: fullPage === "true" }),
4281
+ ...(format === null ? {} : { format }),
4282
+ ...(parsedQuality === null ? {} : { quality: parsedQuality }),
4283
+ };
4284
+ }
4285
+
4286
+ export function browserScreenshotResponse(
4287
+ frame: Pick<BrowserControlFrame, "data" | "mediaType" | "metadataHeader">,
4288
+ ): Response {
4289
+ // Node Buffers can be views into a larger backing allocation. Copy exactly
4290
+ // the image bytes before constructing the Response.
4291
+ return new Response(Uint8Array.from(frame.data).buffer, {
4292
+ status: 200,
4293
+ headers: {
4294
+ "cache-control": "no-store",
4295
+ "content-type": frame.mediaType,
4296
+ "x-opengeni-browser-frame": frame.metadataHeader,
4297
+ },
4298
+ });
4299
+ }
4300
+
4163
4301
  function isUuid(value: unknown): value is string {
4164
4302
  return (
4165
4303
  typeof value === "string" &&
@@ -4716,6 +4854,8 @@ function browserRouteError(error: unknown): HTTPException {
4716
4854
  const connectedMachineError = interactionControlApiError(error, "browser");
4717
4855
  if (connectedMachineError) return connectedMachineError;
4718
4856
  if (error instanceof HTTPException) return error;
4857
+ if (error instanceof SandboxPaidComputeAdmissionError)
4858
+ return new HTTPException(402, { message: error.message, cause: error });
4719
4859
  if (error instanceof BrowserSessionNotFoundError) {
4720
4860
  return new HTTPException(404, { message: error.message, cause: error });
4721
4861
  }