@opengeni/api-router 0.30.3 → 2.1.0-canary.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 (165) hide show
  1. package/dist/api-websocket.d.ts +18 -0
  2. package/dist/app.js +1 -1
  3. package/dist/auth/managed-auth.d.ts +6 -0
  4. package/dist/{chunk-WCXHI3FF.js → chunk-QKDFBBUE.js} +26385 -15573
  5. package/dist/chunk-QKDFBBUE.js.map +1 -0
  6. package/dist/codemode.d.ts +8 -1
  7. package/dist/company-brain-okf.d.ts +24 -0
  8. package/dist/connected-machine-computer-access.d.ts +11 -0
  9. package/dist/connection-authority-owner.d.ts +59 -0
  10. package/dist/connection-ownership.d.ts +47 -0
  11. package/dist/controller-data-plane.d.ts +16 -0
  12. package/dist/editable-artifact-websocket.d.ts +5 -15
  13. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  14. package/dist/http/api-error.d.ts +10 -0
  15. package/dist/http/cors.d.ts +5 -0
  16. package/dist/http/interaction-control-error.d.ts +13 -0
  17. package/dist/http/sse.d.ts +2 -3
  18. package/dist/index.d.ts +5 -2
  19. package/dist/index.js +326 -82
  20. package/dist/index.js.map +1 -1
  21. package/dist/integrations/atlassian.d.ts +17 -0
  22. package/dist/integrations/fiken.d.ts +7 -2
  23. package/dist/integrations/google-drive.d.ts +18 -0
  24. package/dist/integrations/oauth-client.d.ts +22 -3
  25. package/dist/integrations/oauth-profiles.d.ts +195 -0
  26. package/dist/integrations/personal-github-repositories.d.ts +45 -0
  27. package/dist/integrations/personal-github.d.ts +40 -0
  28. package/dist/integrations/pr-review-provider.d.ts +22 -0
  29. package/dist/integrations/provider-oauth.d.ts +8 -0
  30. package/dist/integrations/slack-app-home.d.ts +24 -0
  31. package/dist/integrations/slack-bot.d.ts +149 -9
  32. package/dist/integrations/slack-interactions.d.ts +4 -3
  33. package/dist/integrations/slack-routing.d.ts +120 -0
  34. package/dist/integrations/social-oauth.d.ts +5 -0
  35. package/dist/interaction-frame-proxy.d.ts +68 -0
  36. package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
  37. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  38. package/dist/mcp/documents.d.ts +1 -0
  39. package/dist/mcp/remember.d.ts +28 -0
  40. package/dist/mcp/request-abort.d.ts +19 -0
  41. package/dist/mcp/scheduled-task-view.d.ts +4 -4
  42. package/dist/mcp/server.d.ts +15 -2
  43. package/dist/mcp/session-view.d.ts +4 -0
  44. package/dist/mcp/session-wait.d.ts +137 -0
  45. package/dist/routes/automations.d.ts +4 -0
  46. package/dist/routes/billing.d.ts +5 -0
  47. package/dist/routes/browser-sessions.d.ts +4 -1
  48. package/dist/routes/company-brain.d.ts +3 -0
  49. package/dist/routes/company-profile.d.ts +2 -1
  50. package/dist/routes/connection-authorities.d.ts +3 -0
  51. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  52. package/dist/routes/personal-github.d.ts +3 -0
  53. package/dist/routes/pr-review.d.ts +3 -0
  54. package/dist/routes/sessions.d.ts +1 -0
  55. package/dist/routes/user-resource-authorities.d.ts +3 -0
  56. package/dist/routes/workspace-capture.d.ts +10 -2
  57. package/dist/routes/workspace-learning.d.ts +3 -0
  58. package/dist/routes/workspaces.d.ts +19 -0
  59. package/dist/sandbox/channel-a.d.ts +11 -1
  60. package/dist/sandbox/connection-authority.d.ts +3 -0
  61. package/dist/sandbox/enrollment.d.ts +2 -0
  62. package/dist/sandbox/machines.d.ts +2 -2
  63. package/dist/sandbox/metrics-ingestion.d.ts +10 -20
  64. package/dist/sandbox/viewer.d.ts +24 -1
  65. package/dist/sandbox-file-artifacts.d.ts +11 -0
  66. package/dist/scheduled-task-deletion.d.ts +14 -0
  67. package/dist/slack-reaction-files.d.ts +1 -1
  68. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  69. package/package.json +20 -18
  70. package/src/api-websocket.ts +23 -0
  71. package/src/app.ts +153 -23
  72. package/src/auth/managed-auth.ts +25 -3
  73. package/src/codemode.ts +53 -23
  74. package/src/codex-realtime.ts +8 -2
  75. package/src/company-brain-okf.ts +340 -0
  76. package/src/connected-machine-computer-access.ts +33 -0
  77. package/src/connection-authority-owner.ts +61 -0
  78. package/src/connection-ownership.ts +180 -0
  79. package/src/controller-data-plane.ts +47 -0
  80. package/src/editable-artifact-native-kernel.ts +26 -15
  81. package/src/editable-artifact-office-import.ts +28 -3
  82. package/src/editable-artifact-production.ts +13 -7
  83. package/src/editable-artifact-websocket.ts +11 -18
  84. package/src/editable-artifact-workspace-files.ts +31 -6
  85. package/src/http/api-error.ts +28 -0
  86. package/src/http/auth.ts +4 -0
  87. package/src/http/cors.ts +35 -0
  88. package/src/http/interaction-control-error.ts +164 -0
  89. package/src/http/sse.ts +112 -66
  90. package/src/index.ts +46 -6
  91. package/src/integrations/atlassian.ts +146 -35
  92. package/src/integrations/fiken.ts +102 -22
  93. package/src/integrations/google-drive.ts +301 -92
  94. package/src/integrations/oauth-client.ts +272 -143
  95. package/src/integrations/oauth-profiles.ts +477 -0
  96. package/src/integrations/personal-github-repositories.ts +445 -0
  97. package/src/integrations/personal-github.ts +705 -0
  98. package/src/integrations/pr-review-provider.ts +246 -0
  99. package/src/integrations/provider-oauth.ts +121 -5
  100. package/src/integrations/slack-app-home.ts +300 -0
  101. package/src/integrations/slack-bot.ts +337 -57
  102. package/src/integrations/slack-interactions.ts +1265 -208
  103. package/src/integrations/slack-routing.ts +324 -0
  104. package/src/integrations/social-oauth.ts +25 -0
  105. package/src/interaction-frame-proxy.ts +409 -0
  106. package/src/mcp/company-brain-governed-writes.ts +262 -0
  107. package/src/mcp/company-profile-agent-admin.ts +205 -0
  108. package/src/mcp/documents.ts +37 -6
  109. package/src/mcp/files.ts +16 -1
  110. package/src/mcp/remember.ts +181 -0
  111. package/src/mcp/request-abort.ts +44 -0
  112. package/src/mcp/scheduled-task-view.ts +1 -0
  113. package/src/mcp/server.ts +719 -369
  114. package/src/mcp/session-view.ts +54 -0
  115. package/src/mcp/session-wait.ts +554 -0
  116. package/src/model-catalog.ts +20 -12
  117. package/src/routes/api-integrations.ts +4 -0
  118. package/src/routes/api-keys.ts +1 -0
  119. package/src/routes/automations.ts +534 -0
  120. package/src/routes/billing.ts +57 -1
  121. package/src/routes/browser-sessions.ts +329 -79
  122. package/src/routes/channels.ts +17 -1
  123. package/src/routes/codex.ts +50 -0
  124. package/src/routes/company-brain.ts +367 -0
  125. package/src/routes/company-profile.ts +1 -1
  126. package/src/routes/computer-sessions.ts +320 -93
  127. package/src/routes/connection-authorities.ts +139 -0
  128. package/src/routes/connections.ts +144 -34
  129. package/src/routes/documents.ts +221 -3
  130. package/src/routes/editable-artifacts.ts +11 -3
  131. package/src/routes/enrollments.ts +116 -26
  132. package/src/routes/environments.ts +127 -55
  133. package/src/routes/files.ts +72 -11
  134. package/src/routes/install.ts +82 -28
  135. package/src/routes/integration-facets.ts +29 -0
  136. package/src/routes/interaction-resources.ts +20 -2
  137. package/src/routes/machines.ts +275 -13
  138. package/src/routes/organization-memberships.ts +717 -28
  139. package/src/routes/packs.ts +4 -3
  140. package/src/routes/personal-github-git-broker.ts +785 -0
  141. package/src/routes/personal-github.ts +319 -0
  142. package/src/routes/pr-review.ts +531 -0
  143. package/src/routes/rigs.ts +104 -39
  144. package/src/routes/scheduled-tasks.ts +19 -21
  145. package/src/routes/sessions.ts +775 -38
  146. package/src/routes/social.ts +14 -2
  147. package/src/routes/supergrok.ts +23 -4
  148. package/src/routes/transcription-recordings.ts +8 -2
  149. package/src/routes/user-resource-authorities.ts +138 -0
  150. package/src/routes/workspace-artifacts.ts +4 -1
  151. package/src/routes/workspace-capture.ts +21 -0
  152. package/src/routes/workspace-learning.ts +228 -0
  153. package/src/routes/workspaces.ts +59 -6
  154. package/src/sandbox/auth-callout.ts +38 -12
  155. package/src/sandbox/channel-a.ts +242 -19
  156. package/src/sandbox/connection-authority.ts +3 -0
  157. package/src/sandbox/enrollment.ts +15 -3
  158. package/src/sandbox/machines.ts +186 -64
  159. package/src/sandbox/metrics-ingestion.ts +220 -58
  160. package/src/sandbox/viewer.ts +243 -19
  161. package/src/sandbox-file-artifacts.ts +329 -0
  162. package/src/scheduled-task-deletion.ts +127 -0
  163. package/src/slack-reaction-files.ts +16 -5
  164. package/src/temporal-schedule-cleanup.ts +13 -0
  165. package/dist/chunk-WCXHI3FF.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { type Settings } from "@opengeni/config";
2
- import { OPENGENI_SLACK_BOT_CREDENTIAL_LABEL, OPENGENI_SLACK_BOT_CREDENTIAL_ROLE, type AccessGrant, type ConnectionMetadata, type OpenGeniSlackBotConnectionMetadata } from "@opengeni/contracts";
2
+ import { OPENGENI_SLACK_BOT_CREDENTIAL_LABEL, OPENGENI_SLACK_BOT_CREDENTIAL_ROLE, type AccessGrant, type ConnectionMetadata, type OpenGeniSlackBotDisplayName, type OpenGeniSlackBotConnectionMetadata } from "@opengeni/contracts";
3
3
  import { type Database } from "@opengeni/db";
4
4
  import { type FetchLike } from "@opengeni/network";
5
5
  import { HTTPException } from "hono/http-exception";
@@ -34,12 +34,16 @@ export type DownloadedSlackReactionImage = Readonly<{
34
34
  contentType: "image/png" | "image/jpeg" | "image/webp";
35
35
  bytes: Uint8Array;
36
36
  }>;
37
+ export type ExchangedOpenGeniSlackAuthorization = Readonly<{
38
+ accessToken: string;
39
+ appId: string;
40
+ }>;
37
41
  export declare function exchangeOpenGeniSlackAuthorizationCode(input: {
38
42
  code: string;
39
43
  clientId: string;
40
44
  clientSecret: string;
41
45
  redirectUri: string;
42
- }, fetchImpl?: FetchLike): Promise<string>;
46
+ }, fetchImpl?: FetchLike): Promise<ExchangedOpenGeniSlackAuthorization>;
43
47
  export type SlackBotReceipt = {
44
48
  credentialRole: typeof OPENGENI_SLACK_BOT_CREDENTIAL_ROLE;
45
49
  credentialLabel: typeof OPENGENI_SLACK_BOT_CREDENTIAL_LABEL;
@@ -74,7 +78,58 @@ export type SlackMessageBlock = {
74
78
  } | {
75
79
  type: "divider";
76
80
  };
77
- type SlackBotOperation = "channels.list" | "channel_history.read" | "thread_replies.read" | "users.list" | "files.list" | "file.info" | "file.content.read" | "message.post" | "message.update" | "message.delete";
81
+ export type SlackHomeBlock = {
82
+ type: "header";
83
+ block_id?: string;
84
+ text: {
85
+ type: "plain_text";
86
+ text: string;
87
+ emoji?: boolean;
88
+ };
89
+ } | {
90
+ type: "section";
91
+ block_id?: string;
92
+ text: {
93
+ type: "mrkdwn" | "plain_text";
94
+ text: string;
95
+ emoji?: boolean;
96
+ };
97
+ accessory?: {
98
+ type: "button";
99
+ action_id: string;
100
+ text: {
101
+ type: "plain_text";
102
+ text: string;
103
+ emoji?: boolean;
104
+ };
105
+ url: string;
106
+ };
107
+ } | {
108
+ type: "context";
109
+ block_id?: string;
110
+ elements: Array<{
111
+ type: "mrkdwn" | "plain_text";
112
+ text: string;
113
+ emoji?: boolean;
114
+ }>;
115
+ } | {
116
+ type: "actions";
117
+ block_id: string;
118
+ elements: Array<{
119
+ type: "button";
120
+ action_id: string;
121
+ text: {
122
+ type: "plain_text";
123
+ text: string;
124
+ emoji?: boolean;
125
+ };
126
+ url: string;
127
+ style?: "primary" | "danger";
128
+ }>;
129
+ } | {
130
+ type: "divider";
131
+ };
132
+ type SlackBotOperation = "channels.list" | "search.context" | "channel_history.read" | "thread_replies.read" | "users.list" | "files.list" | "file.info" | "file.content.read" | "home.publish" | "message.post" | "message.update" | "message.delete";
78
133
  type SlackBotContext = {
79
134
  accountId: string;
80
135
  workspaceId: string;
@@ -82,6 +137,7 @@ type SlackBotContext = {
82
137
  sessionId?: string | null;
83
138
  scheduledTaskId?: string | null;
84
139
  };
140
+ type SlackProviderAuthorization = () => Promise<boolean | void>;
85
141
  export type SlackReactionContextCheckpointBinding = {
86
142
  inboxId: string;
87
143
  accountId: string;
@@ -125,6 +181,14 @@ export declare class SlackBotProviderError extends Error {
125
181
  readonly retryAfterMs: number | null;
126
182
  constructor(code: string, retryAfterMs?: number | null);
127
183
  }
184
+ export declare function authorizeSlackSharedImageRead(channel: {
185
+ isArchived: boolean;
186
+ isShared: boolean;
187
+ isExternallyShared: boolean;
188
+ isOrgShared: boolean;
189
+ isPendingExternallyShared: boolean;
190
+ isMpim: boolean;
191
+ }, authorizeSharedRead: (() => Promise<void>) | undefined): Promise<void>;
128
192
  export type SlackBotCredentialVerificationFailureReason = "scope_mismatch" | "identity_mismatch";
129
193
  export declare class SlackBotCredentialVerificationError extends HTTPException {
130
194
  readonly failureReason: SlackBotCredentialVerificationFailureReason;
@@ -132,11 +196,14 @@ export declare class SlackBotCredentialVerificationError extends HTTPException {
132
196
  }
133
197
  /**
134
198
  * Validates a write-only xoxb credential before it can enter encrypted storage.
135
- * Slack does not expose the app's display_information name to this scope set;
136
- * users.info provides the authoritative installed bot display name, while the
137
- * documented manifest fixes the app name itself to the same exact value.
199
+ * The OAuth exchange's app_id is the immutable app authority. Slack bot profile
200
+ * names are mutable presentation data and may lag manifest/App Home changes, so
201
+ * they must not decide whether a credential belongs to this installation.
138
202
  */
139
- export declare function verifyOpenGeniSlackBotCredential(token: string, fetchImpl?: FetchLike, now?: Date): Promise<VerifiedOpenGeniSlackBot>;
203
+ export declare function verifyOpenGeniSlackBotCredential(token: string, expected: Readonly<{
204
+ appId: string;
205
+ displayName: OpenGeniSlackBotDisplayName;
206
+ }>, fetchImpl?: FetchLike, now?: Date): Promise<VerifiedOpenGeniSlackBot>;
140
207
  export declare function resolveSlackBotConnectionForTool(input: {
141
208
  db: Database;
142
209
  grant: AccessGrant;
@@ -154,8 +221,9 @@ export declare class OpenGeniSlackBotClient {
154
221
  private readonly metadata;
155
222
  private readonly context;
156
223
  private readonly fetchImpl;
224
+ private readonly authorizeProviderRequest?;
157
225
  private readonly resolveCredential;
158
- constructor(db: Database, settings: Settings, connection: ConnectionMetadata, metadata: OpenGeniSlackBotConnectionMetadata, context: SlackBotContext, fetchImpl?: FetchLike);
226
+ constructor(db: Database, settings: Settings, connection: ConnectionMetadata, metadata: OpenGeniSlackBotConnectionMetadata, context: SlackBotContext, fetchImpl?: FetchLike, authorizeProviderRequest?: SlackProviderAuthorization | undefined);
159
227
  listChannels(input?: {
160
228
  limit?: number;
161
229
  cursor?: string;
@@ -202,6 +270,20 @@ export declare class OpenGeniSlackBotClient {
202
270
  purpose: string;
203
271
  numMembers: number | null;
204
272
  }>;
273
+ /**
274
+ * Replace one Slack user's private App Home view. `views.publish` is a
275
+ * naturally convergent replace operation: Slack event retries may safely
276
+ * repeat the exact bounded view without creating duplicate provider state.
277
+ */
278
+ publishHomeView(input: {
279
+ userId: string;
280
+ blocks: SlackHomeBlock[];
281
+ hash?: string | null;
282
+ }): Promise<{
283
+ viewId: string;
284
+ } & {
285
+ receipt: SlackBotReceipt;
286
+ }>;
205
287
  slackTaskPolicyFacts(channelId: string, userId: string): Promise<({
206
288
  conversation: {
207
289
  id: string;
@@ -433,9 +515,10 @@ export declare class OpenGeniSlackBotClient {
433
515
  name: string;
434
516
  title: string;
435
517
  }[];
518
+ authorizeSharedRead?: () => Promise<void>;
436
519
  }): Promise<PreparedSlackReactionImage[]>;
437
520
  /** Download and fully validate one previously authorized Slack image. */
438
- downloadReactionImage(input: PreparedSlackReactionImage): Promise<DownloadedSlackReactionImage>;
521
+ downloadReactionImage(input: PreparedSlackReactionImage, authorizeSharedRead?: () => Promise<void>): Promise<DownloadedSlackReactionImage>;
439
522
  listUsers(input?: {
440
523
  limit?: number;
441
524
  cursor?: string;
@@ -452,6 +535,61 @@ export declare class OpenGeniSlackBotClient {
452
535
  } & {
453
536
  receipt: SlackBotReceipt;
454
537
  }>;
538
+ /**
539
+ * Workspace-wide public search through Slack's Real-time Search API
540
+ * (`assistant.search.context`) under the bot identity.
541
+ *
542
+ * The bot never carries private-search authority: `channel_types` is pinned
543
+ * to `public_channel` server-side regardless of caller input, so private
544
+ * channels, DMs, and MPIMs remain reachable only through a member's personal
545
+ * hosted-MCP grant. An install predating the search scopes fails closed with
546
+ * a reinstall hint instead of leaking Slack's `missing_scope` error.
547
+ */
548
+ searchContext(input: {
549
+ query: string;
550
+ contentTypes?: readonly ("messages" | "files" | "channels")[];
551
+ includeBots?: boolean;
552
+ /** UNIX seconds bounds on message timestamps. */
553
+ before?: number;
554
+ after?: number;
555
+ sort?: "score" | "timestamp";
556
+ sortDir?: "asc" | "desc";
557
+ cursor?: string;
558
+ limit?: number;
559
+ }): Promise<{
560
+ messages: {
561
+ channelId: string;
562
+ channelName: string;
563
+ ts: string;
564
+ authorUserId: string;
565
+ authorName: string;
566
+ isAuthorBot: boolean;
567
+ content: string;
568
+ permalink: string | null;
569
+ }[];
570
+ files: {
571
+ id: string;
572
+ title: string;
573
+ filetype: string;
574
+ authorUserId: string;
575
+ authorName: string;
576
+ dateCreated: number | null;
577
+ dateUpdated: number | null;
578
+ content: string;
579
+ permalink: string | null;
580
+ }[];
581
+ channels: {
582
+ name: string;
583
+ topic: string;
584
+ purpose: string;
585
+ creatorUserId: string;
586
+ dateCreated: number | null;
587
+ permalink: string | null;
588
+ }[];
589
+ nextCursor: string | null;
590
+ } & {
591
+ receipt: SlackBotReceipt;
592
+ }>;
455
593
  listFiles(input: {
456
594
  channelId: string;
457
595
  limit?: number;
@@ -641,11 +779,13 @@ export declare function createOpenGeniSlackBotClient(deps: {
641
779
  db: Database;
642
780
  settings: Settings;
643
781
  slackFetch?: typeof fetch;
782
+ authorizeProviderRequest?: SlackProviderAuthorization;
644
783
  }, resolved: Awaited<ReturnType<typeof resolveSlackBotConnectionForTool>>): OpenGeniSlackBotClient;
645
784
  export declare function createOpenGeniSlackBotInteractionClient(deps: {
646
785
  db: Database;
647
786
  settings: Settings;
648
787
  slackFetch?: typeof fetch;
788
+ authorizeProviderRequest?: SlackProviderAuthorization;
649
789
  }, input: {
650
790
  accountId: string;
651
791
  workspaceId: string;
@@ -6,7 +6,7 @@ import { type OpenGeniSlackBotClient } from "./slack-bot.js";
6
6
  import { type ImportedSlackReactionImage } from "../slack-reaction-files.js";
7
7
  export declare const SLACK_INTERACTION_MAX_BODY_BYTES: number;
8
8
  export declare const SLACK_SIGNATURE_REPLAY_WINDOW_SECONDS = 300;
9
- export declare const SLACK_DELIVERY_EVENT_TYPES: readonly ["agent.message.completed", "session.humanInput.requested", "session.requiresAction", "turn.completed", "turn.failed", "turn.cancelled", "session.status.changed"];
9
+ export declare const SLACK_DELIVERY_EVENT_TYPES: readonly ["agent.message.completed", "session.humanInput.requested", "session.requiresAction", "turn.completed", "turn.failed", "turn.cancelled", "session.status.changed", "system.update.pending", "goal.paused"];
10
10
  /**
11
11
  * Slack delivery restrictions are durable session-level authority, not
12
12
  * attacker-adjacent user-message context. Migration 0240 backfills this exact
@@ -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" | "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" | "interaction_discover" | "interaction_request_human" | "memory_correct" | "memory_save" | "memory_search" | "preference_registry_get" | "preference_registry_summary" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "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_pause" | "session_resume" | "session_send_message" | "session_steer" | "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_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_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" | "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;
@@ -58,7 +58,7 @@ export type SlackInvocationMessageContext = {
58
58
  nextCursor: string | null;
59
59
  kind: "thread" | "channel";
60
60
  };
61
- export declare function slackInvocationTaskText(entry: Pick<SlackInteractionInboxEntry, "slackMessageTs" | "slackUserId" | "text">, context: SlackInvocationMessageContext): string;
61
+ export declare function slackInvocationModelContext(invocationTimestamp: string, context: SlackInvocationMessageContext): string;
62
62
  type SlackReactionMessageContext = Awaited<ReturnType<OpenGeniSlackBotClient["reactionMessageContext"]>>;
63
63
  type PreparedSlackReactionTask = Readonly<{
64
64
  resources: FileResourceRef[];
@@ -69,6 +69,7 @@ type PreparedSlackReactionTask = Readonly<{
69
69
  export declare function slackReactionTaskText(context: SlackReactionMessageContext, prepared?: PreparedSlackReactionTask): string;
70
70
  export declare function slackDeliveryTextsCoalesce(left: string, right: string): boolean;
71
71
  export declare function normalizedBlockActionInteraction(payload: Record<string, unknown>): NormalizedSlackInteraction;
72
+ export declare function isSlackInfoCommand(text: string): boolean;
72
73
  type SlackUserLinkToken = {
73
74
  workspaceId: string;
74
75
  connectionId: string;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Per-channel and per-DM Slack workspace routing.
3
+ *
4
+ * Routing is decided BEFORE any agent runs, from durable facts only, so it can
5
+ * never be a model judgement. The resolver is pure with respect to the database
6
+ * reads it is handed: it returns a decision, and the caller persists and acts on
7
+ * it.
8
+ *
9
+ * Two rules are load-bearing and easy to break by accident:
10
+ *
11
+ * A mapped thread keeps the workspace it was created in, unconditionally,
12
+ * including after the channel has been re-pointed. Otherwise a live
13
+ * conversation would silently change tenants mid-thread.
14
+ *
15
+ * A subject who lacks access to the routed workspace is NEVER quietly served
16
+ * from another workspace they happen to belong to. Every failure is explicit
17
+ * and creates no session.
18
+ */
19
+ import type { AccessGrant } from "@opengeni/contracts";
20
+ import type { SlackChannelRoute, SlackInteractionInboxEntry, SlackRoutableWorkspace, SlackUserDmRoute } from "@opengeni/db";
21
+ export type SlackRouteTenancy = {
22
+ accountId: string;
23
+ workspaceId: string;
24
+ };
25
+ export type SlackRouteResolution = (SlackRouteTenancy & {
26
+ kind: "resolved";
27
+ label: string | null;
28
+ source: "thread" | "prefix" | "channel" | "dm_route" | "dm_personal" | "sole_candidate" | "installation";
29
+ }) | {
30
+ kind: "ask";
31
+ candidates: readonly SlackRoutableWorkspace[];
32
+ } | {
33
+ kind: "denied";
34
+ reason: "no_access_to_named" | "no_access_to_route" | "no_candidates";
35
+ requested: string | null;
36
+ candidates: readonly SlackRoutableWorkspace[];
37
+ };
38
+ /**
39
+ * A Slack direct message to the bot, by any trigger that can carry one.
40
+ *
41
+ * `isDirectMessageShortcut` is deliberately narrower: it means "a message
42
+ * shortcut invoked inside a DM", which is a private-handoff concern. Routing
43
+ * cares about the broader question of whether this conversation is one human's
44
+ * private channel with the bot, which is also true of an ordinary `dm` event.
45
+ */
46
+ export declare function isSlackDirectMessageConversation(entry: Pick<SlackInteractionInboxEntry, "triggerKind" | "slackChannelId">): boolean;
47
+ /**
48
+ * Split a leading bot mention off the message text.
49
+ *
50
+ * Slack delivers an `app_mention` with the mention still in the text
51
+ * (`<@U123> do the thing`), and OpenGeni stores it verbatim. The workspace
52
+ * prefix is only an override when it is the first thing the person typed, so it
53
+ * is parsed after the mention rather than at byte 0 of the raw text. Everything
54
+ * split off here is put back, so the message the model sees is unchanged apart
55
+ * from the addressing the person used to route it.
56
+ */
57
+ export declare function splitSlackLeadingMention(text: string, botUserId: string | null): {
58
+ lead: string;
59
+ rest: string;
60
+ };
61
+ /**
62
+ * The strict `in <workspace>: ...` override.
63
+ *
64
+ * Parsed only at byte 0, so ordinary prose that happens to contain the word
65
+ * cannot trigger it, and matched case-insensitively against the exact label of a
66
+ * workspace the subject can already start work in. A prefix that names nothing
67
+ * recognizable is NOT a suggestion that falls through: it is a refusal, because
68
+ * silently ignoring an explicit override is how a message lands somewhere the
69
+ * person did not intend.
70
+ */
71
+ export declare function parseSlackWorkspacePrefix(text: string): {
72
+ requested: string;
73
+ remainder: string;
74
+ } | null;
75
+ export type SlackRouteInputs = {
76
+ /** The installation binding's tenancy. Always the fallback, never a silent one. */
77
+ home: SlackRouteTenancy;
78
+ entry: Pick<SlackInteractionInboxEntry, "triggerKind" | "slackChannelId" | "slackUserId" | "text">;
79
+ /**
80
+ * The tenancy of an interaction already mapped to this thread, if any. A
81
+ * mapped thread wins unconditionally.
82
+ */
83
+ threadTenancy: SlackRouteTenancy | null;
84
+ channelRoute: SlackChannelRoute | null;
85
+ dmRoute: SlackUserDmRoute | null;
86
+ /** The subject's own personal workspace in the home organization, if any. */
87
+ personalWorkspaceId: string | null;
88
+ /** Workspaces this subject may actually start work in, ordered stably. */
89
+ candidates: readonly SlackRoutableWorkspace[];
90
+ /** The installation's bot user, so a mention does not hide the prefix. */
91
+ botUserId: string | null;
92
+ /** `OPENGENI_SLACK_WORKSPACE_ROUTING_ENABLED`. */
93
+ routingEnabled: boolean;
94
+ /** Whether the first-use picker exists yet. Until it does, ambiguity keeps home. */
95
+ askEnabled: boolean;
96
+ };
97
+ /**
98
+ * Decide which workspace this Slack message starts work in.
99
+ *
100
+ * First match wins, and the order is deliberate: continuity beats an explicit
101
+ * override beats configuration beats derivation beats asking.
102
+ */
103
+ export declare function resolveSlackWorkspaceRoute(input: SlackRouteInputs): SlackRouteResolution;
104
+ /**
105
+ * The message text the model actually sees.
106
+ *
107
+ * A prefix override is addressing information, not part of the request, so it is
108
+ * stripped exactly once and only when it matched.
109
+ */
110
+ export declare function slackRoutedRequestText(text: string, resolution: SlackRouteResolution, botUserId: string | null): string;
111
+ export type SlackRouteAuthorization = {
112
+ kind: "authorized";
113
+ grant: AccessGrant;
114
+ tenancy: SlackRouteTenancy;
115
+ label: string | null;
116
+ } | {
117
+ kind: "denied";
118
+ reason: "no_access_to_route";
119
+ tenancy: SlackRouteTenancy;
120
+ };
@@ -45,6 +45,11 @@ export type SocialOAuthStartContext = {
45
45
  accountId: string;
46
46
  workspaceId: string;
47
47
  subjectId: string;
48
+ /**
49
+ * False for every principal that cannot own a personal Connection. Resolved
50
+ * by the route from the live authenticated principal, never inferred here.
51
+ */
52
+ personalOwnershipAllowed: boolean;
48
53
  requestUrl: string;
49
54
  payload: SocialOAuthStartRequest;
50
55
  };
@@ -0,0 +1,68 @@
1
+ import type { ApiWebSocketConnection, ApiWebSocketLike, ApiWebSocketUpgradeServer } from "./api-websocket.js";
2
+ export declare const INTERACTION_FRAME_PROXY_PATH = "/v1/interaction/frame-proxy";
3
+ export declare const INTERACTION_FRAME_PROXY_PROTOCOL_PREFIX = "opengeni-frame-proxy.";
4
+ type ProxyGrant = Readonly<{
5
+ version: 1;
6
+ upstreamUrl: string;
7
+ upstreamProtocols: readonly string[];
8
+ responseProtocol: string;
9
+ origin: string | null;
10
+ expiresAt: number;
11
+ }>;
12
+ export type InteractionFrameProxyAttachment = Readonly<{
13
+ url: string;
14
+ protocols: readonly string[];
15
+ }>;
16
+ /** Docker boxes cannot carry browserd's WebSocket subprotocol grant to the
17
+ * viewer. Unsigned OpenSandbox Channel B still uses the lifecycle proxy, so
18
+ * it needs the same hatch. Signed URI-mode ingress keeps native subprotocols;
19
+ * `openSandboxInteractionFrameProxy` is an emergency override only. */
20
+ export declare function placementUsesInteractionFrameProxy(backend: string | null | undefined, options?: {
21
+ openSandboxSignedEndpoints?: boolean;
22
+ openSandboxInteractionFrameProxy?: boolean;
23
+ }): boolean;
24
+ /** Public origin the browser can open. TLS-terminating reverse proxies make
25
+ * `requestUrl` `http://127.0.0.1` / the API container; that would mint `ws://`
26
+ * and Chrome blocks it from an `https://` console. */
27
+ export declare function resolveInteractionFrameProxyRequestUrl(input: {
28
+ requestUrl: string;
29
+ publicBaseUrl?: string | undefined;
30
+ webBaseUrl?: string | undefined;
31
+ forwardedProto?: string | null | undefined;
32
+ forwardedHost?: string | null | undefined;
33
+ }): string;
34
+ export declare function createInteractionFrameProxyAttachment(input: {
35
+ requestUrl: string;
36
+ publicBaseUrl?: string | undefined;
37
+ webBaseUrl?: string | undefined;
38
+ forwardedProto?: string | null | undefined;
39
+ forwardedHost?: string | null | undefined;
40
+ rootSecret: string;
41
+ upstreamUrl: string;
42
+ upstreamProtocols: readonly string[];
43
+ origin: string | null;
44
+ expiresAt: string;
45
+ }): InteractionFrameProxyAttachment;
46
+ export declare class InteractionFrameProxyTransport {
47
+ private readonly rootSecret;
48
+ private readonly clock;
49
+ constructor(rootSecret: string | undefined, clock?: () => number);
50
+ handles(request: Request): boolean;
51
+ upgrade(request: Request, server: ApiWebSocketUpgradeServer): Response | undefined;
52
+ }
53
+ export declare class InteractionFrameProxyConnection implements ApiWebSocketConnection {
54
+ private readonly grant;
55
+ private socket;
56
+ private upstream;
57
+ private upstreamOpen;
58
+ private terminal;
59
+ private queued;
60
+ private queuedBytes;
61
+ constructor(grant: ProxyGrant);
62
+ attach(socket: ApiWebSocketLike): void;
63
+ receive(message: string | Uint8Array | ArrayBuffer): void;
64
+ transportClosed(): void;
65
+ private fail;
66
+ private clear;
67
+ }
68
+ export {};
@@ -0,0 +1,26 @@
1
+ import { type CompanyBrainGovernedWriteAttempt } from "@opengeni/contracts";
2
+ import { createCompanyBrainLearningPolicyRouter } from "@opengeni/core";
3
+ import type { Database } from "@opengeni/db";
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ type JsonResult = (value: unknown) => {
6
+ content: {
7
+ type: "text";
8
+ text: string;
9
+ }[];
10
+ };
11
+ export type RegisterCompanyBrainGovernedWriteToolsInput = {
12
+ server: McpServer;
13
+ db: Database;
14
+ attempt: CompanyBrainGovernedWriteAttempt;
15
+ authorize: () => Promise<void>;
16
+ json: JsonResult;
17
+ router?: Pick<ReturnType<typeof createCompanyBrainLearningPolicyRouter>, "write">;
18
+ };
19
+ /**
20
+ * Register explicit proposal-only Company Brain tools. The signed host attempt
21
+ * owns tenant/session authority and the exact evidence id owns the learning
22
+ * policy source override. Tool input cannot select scope, active authority, or
23
+ * another policy source.
24
+ */
25
+ export declare function registerCompanyBrainGovernedWriteTools(input: RegisterCompanyBrainGovernedWriteToolsInput): void;
26
+ export {};
@@ -0,0 +1,44 @@
1
+ import { type CompanyProfileAgentAttempt, type CompanyProfileEntry } from "@opengeni/contracts";
2
+ import { createCompanyProfileAgentAdminRouter } from "@opengeni/core";
3
+ import type { Database } from "@opengeni/db";
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import * as z from "zod/v4";
6
+ type JsonResult = (value: unknown) => {
7
+ content: {
8
+ type: "text";
9
+ text: string;
10
+ }[];
11
+ };
12
+ export type RegisterCompanyProfileAgentAdminToolsInput = {
13
+ server: McpServer;
14
+ db: Database;
15
+ attempt: CompanyProfileAgentAttempt;
16
+ authorize: () => Promise<void>;
17
+ json: JsonResult;
18
+ router?: Pick<ReturnType<typeof createCompanyProfileAgentAdminRouter>, "propose" | "confirm">;
19
+ };
20
+ declare const entry: z.ZodObject<{
21
+ key: z.ZodOptional<z.ZodString>;
22
+ content: z.ZodString;
23
+ }, z.core.$strip>;
24
+ type EntryInput = z.infer<typeof entry>;
25
+ /**
26
+ * Derive a stable key from entry content: the first few words, normalized into
27
+ * the company-profile key alphabet and bounded to the stable-key length.
28
+ */
29
+ export declare function deriveCompanyProfileStableKey(content: string): string;
30
+ /**
31
+ * Resolve explicit or derived keys for one list and de-duplicate by suffixing
32
+ * `-2`, `-3`, ... so the profile passes the unique-key contract.
33
+ */
34
+ export declare function resolveCompanyProfileEntries(input: readonly EntryInput[]): CompanyProfileEntry[];
35
+ /**
36
+ * Register the explicit owner-confirmed organization profile path. This is
37
+ * administration, not derived learning: workspace learning mode is never
38
+ * consulted, the initiating human must be the organization's active owner (the
39
+ * same authority as the manual `account:admin` route), and the proposal cannot
40
+ * become active without the exact bound human-input response returned by the
41
+ * first call.
42
+ */
43
+ export declare function registerCompanyProfileAgentAdminTools(input: RegisterCompanyProfileAgentAdminToolsInput): void;
44
+ export {};
@@ -3,6 +3,7 @@ import { type Database } from "@opengeni/db";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  export declare function buildDocumentsMcpServer(db: Database, accountId: string, workspaceId: string, documentServices: DocumentServices, options: {
5
5
  createdBySessionId?: string | undefined;
6
+ attemptId?: string | undefined;
6
7
  /** Immutable human subject whose agent is making this retrieval request. */
7
8
  initiatingSubjectId: string;
8
9
  }): McpServer;
@@ -0,0 +1,28 @@
1
+ import { type CompanyBrainGovernedWriteAttempt } from "@opengeni/contracts";
2
+ import { createRememberRouter } from "@opengeni/core";
3
+ import type { Database } from "@opengeni/db";
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ type JsonResult = (value: unknown) => {
6
+ content: {
7
+ type: "text";
8
+ text: string;
9
+ }[];
10
+ };
11
+ export type RegisterRememberToolsInput = {
12
+ server: McpServer;
13
+ db: Database;
14
+ attempt: CompanyBrainGovernedWriteAttempt;
15
+ authorize: () => Promise<void>;
16
+ json: JsonResult;
17
+ router?: Pick<ReturnType<typeof createRememberRouter>, "remember" | "confirm">;
18
+ };
19
+ /**
20
+ * Explicit user-directed remember. Content becomes exact task-note evidence,
21
+ * promotion runs through the frozen workspace learning policy, and activation
22
+ * is either automatic (preference under Automatic mode) or requires exactly one
23
+ * bound human confirmation through the built-in `request_human_input` tool.
24
+ * Tool input cannot select scope beyond the workspace, active authority, or
25
+ * another learning-policy source.
26
+ */
27
+ export declare function registerRememberTools(input: RegisterRememberToolsInput): void;
28
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
2
+ /**
3
+ * Serve one first-party MCP POST and bind the tool handlers' `extra.signal` to
4
+ * the HTTP client's connection.
5
+ *
6
+ * The API builds a fresh transport plus `McpServer` per POST, so an MCP
7
+ * `notifications/cancelled` from the worker would land on a different Protocol
8
+ * instance and never reach the handler. The only cancellation evidence this
9
+ * process can observe is the HTTP request's own abort (`Request.signal`, which
10
+ * Bun fires when the client disconnects). Closing the transport runs the SDK
11
+ * `Protocol._onclose`, which aborts every in-flight request handler's
12
+ * controller, so a blocking tool such as `session_wait` stops promptly when the
13
+ * worker drops the call (Steer, Pause, turn interruption).
14
+ *
15
+ * In JSON-response mode the SDK's `handleRequest` promise never settles once
16
+ * its stream mapping is cleared, so the race resolves a client-gone response
17
+ * instead of leaving the route handler pending.
18
+ */
19
+ export declare function handleMcpRequestWithClientAbort(transport: Pick<WebStandardStreamableHTTPServerTransport, "handleRequest" | "close">, request: Request, clientSignal: AbortSignal | undefined): Promise<Response>;