@opengeni/api-router 0.30.3 → 2.2.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-3TP54PPX.js} +26692 -15389
  5. package/dist/chunk-3TP54PPX.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 +157 -9
  32. package/dist/integrations/slack-interactions.d.ts +39 -5
  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 +161 -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 +342 -57
  102. package/src/integrations/slack-interactions.ts +1898 -259
  103. package/src/integrations/slack-routing.ts +337 -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 +353 -4
  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,10 +1,15 @@
1
1
  import { z } from "zod";
2
- import { CreateChannelRequest, UpdateChannelRequest } from "@opengeni/contracts";
2
+ import {
3
+ CreateChannelRequest,
4
+ ReorderChannelsRequest,
5
+ UpdateChannelRequest,
6
+ } from "@opengeni/contracts";
3
7
  import {
4
8
  ChannelNameConflictError,
5
9
  createChannel,
6
10
  deleteChannel,
7
11
  listChannels,
12
+ reorderChannels,
8
13
  updateChannel,
9
14
  } from "@opengeni/db";
10
15
  import type { Hono } from "hono";
@@ -78,6 +83,17 @@ export function registerChannelRoutes(app: Hono, deps: ApiRouteDeps): void {
78
83
  }
79
84
  });
80
85
 
86
+ app.put("/v1/workspaces/:workspaceId/channels/order", async (c) => {
87
+ const workspaceId = c.req.param("workspaceId");
88
+ await requireAccessGrant(c, deps, workspaceId, "sessions:create");
89
+ const payload = ReorderChannelsRequest.parse(await c.req.json());
90
+ const channels = await reorderChannels(db, workspaceId, payload.channelIds);
91
+ if (!channels) {
92
+ throw new HTTPException(409, { message: "projects changed; refresh and try again" });
93
+ }
94
+ return c.json(channels);
95
+ });
96
+
81
97
  app.delete("/v1/workspaces/:workspaceId/channels/:channelId", async (c) => {
82
98
  const workspaceId = c.req.param("workspaceId");
83
99
  await requireAccessGrant(c, deps, workspaceId, "sessions:create");
@@ -386,10 +386,46 @@ function createProviderCallLimiter(limit: number): CodexProviderCall {
386
386
  };
387
387
  }
388
388
 
389
+ type CodexRedemptionAccess = {
390
+ ownership: "current_human" | "unowned" | "different_human" | "managed_human_unavailable";
391
+ canClaimUnownedViaReconnect: boolean;
392
+ };
393
+
394
+ function codexRedemptionAccess(input: {
395
+ connectedBySubjectId: string | null;
396
+ grantSubjectId: string;
397
+ managedHumanSubjectId: string | null;
398
+ canManage: boolean;
399
+ }): CodexRedemptionAccess {
400
+ if (
401
+ input.managedHumanSubjectId === null ||
402
+ input.managedHumanSubjectId !== input.grantSubjectId
403
+ ) {
404
+ return {
405
+ ownership: "managed_human_unavailable",
406
+ canClaimUnownedViaReconnect: false,
407
+ };
408
+ }
409
+ if (input.connectedBySubjectId === null) {
410
+ return {
411
+ ownership: "unowned",
412
+ canClaimUnownedViaReconnect: input.canManage,
413
+ };
414
+ }
415
+ return {
416
+ ownership:
417
+ input.connectedBySubjectId === input.managedHumanSubjectId
418
+ ? "current_human"
419
+ : "different_human",
420
+ canClaimUnownedViaReconnect: false,
421
+ };
422
+ }
423
+
389
424
  async function fetchCodexAccountOverview(
390
425
  deps: ApiRouteDeps,
391
426
  workspaceId: string,
392
427
  row: CodexAccountStatus,
428
+ redemptionAccess: CodexRedemptionAccess,
393
429
  canRedeem: boolean,
394
430
  canResumeRedemption: boolean,
395
431
  redemptions: Awaited<ReturnType<typeof listCodexResetRedemptionRecoveries>> = [],
@@ -496,6 +532,7 @@ async function fetchCodexAccountOverview(
496
532
  })),
497
533
  },
498
534
  canRedeem,
535
+ redemptionAccess,
499
536
  canResumeRedemption,
500
537
  redemptions: redemptions.map((redemption) => ({
501
538
  attemptId: redemption.attemptId,
@@ -1166,10 +1203,17 @@ export function registerCodexRoutes(app: Hono, deps: ApiRouteDeps): void {
1166
1203
  hasPermission(grant.permissions, "workspace:admin"),
1167
1204
  );
1168
1205
  const canRedeem = canResumeRedemption && account.status === "active";
1206
+ const redemptionAccess = codexRedemptionAccess({
1207
+ connectedBySubjectId: account.connectedBySubjectId,
1208
+ grantSubjectId: grant.subjectId,
1209
+ managedHumanSubjectId: human?.subjectId ?? null,
1210
+ canManage: hasPermission(grant.permissions, "workspace:admin"),
1211
+ });
1169
1212
  overview[account.id] = await fetchCodexAccountOverview(
1170
1213
  deps,
1171
1214
  workspaceId,
1172
1215
  account,
1216
+ redemptionAccess,
1173
1217
  canRedeem,
1174
1218
  canResumeRedemption,
1175
1219
  canResumeRedemption
@@ -1216,6 +1260,12 @@ export function registerCodexRoutes(app: Hono, deps: ApiRouteDeps): void {
1216
1260
  deps,
1217
1261
  workspaceId,
1218
1262
  account,
1263
+ codexRedemptionAccess({
1264
+ connectedBySubjectId: account.connectedBySubjectId,
1265
+ grantSubjectId: grant.subjectId,
1266
+ managedHumanSubjectId: human?.subjectId ?? null,
1267
+ canManage: hasPermission(grant.permissions, "workspace:admin"),
1268
+ }),
1219
1269
  false,
1220
1270
  canResumeRedemption,
1221
1271
  canResumeRedemption
@@ -0,0 +1,367 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ COMPANY_BRAIN_INSPECTOR_CURSOR_MAX_CHARS,
4
+ COMPANY_BRAIN_INSPECTOR_DEFAULT_LIMIT,
5
+ COMPANY_BRAIN_INSPECTOR_MAX_LIMIT,
6
+ CompanyBrainContextReceiptPage,
7
+ CompanyBrainKnowledgeProposalPage,
8
+ CompanyBrainOkfPackage,
9
+ KnowledgeBrowseRequest,
10
+ KnowledgeBrowseResponse,
11
+ KnowledgeGetResponse,
12
+ KnowledgeRecordId,
13
+ KnowledgeSearchRequest,
14
+ KnowledgeSearchResponse,
15
+ type WorkspaceInstructionPolicyRevision,
16
+ } from "@opengeni/contracts";
17
+ import { hasPermission, requireAccessGrant, type ApiRouteDeps } from "@opengeni/core";
18
+ import {
19
+ getCurrentPreferenceRegistryGovernanceMetadata,
20
+ getWorkspace,
21
+ getWorkspaceInstructionPolicyRevision,
22
+ inspectCompanyBrainContextReceipts,
23
+ listActivatedCompanyBrainPolicyRevisionIds,
24
+ listCompanyBrainKnowledgeProposals,
25
+ listCompanyBrainPreferenceGuidance,
26
+ listCompanyProfile,
27
+ listWorkspaceInstructionPolicyRevisions,
28
+ listWorkspaceStateMemoryRecords,
29
+ } from "@opengeni/db";
30
+ import {
31
+ browseEffectiveKnowledge,
32
+ getDocumentInventory,
33
+ getEffectiveKnowledgeRecord,
34
+ searchEffectiveKnowledge,
35
+ } from "@opengeni/documents";
36
+ import type { Hono } from "hono";
37
+ import { HTTPException } from "hono/http-exception";
38
+ import { z } from "zod";
39
+
40
+ import { createCompanyBrainOkfPackage, serializeCompanyBrainOkf } from "../company-brain-okf";
41
+ import { projectWorkspaceState } from "../workspace-state-projection";
42
+
43
+ const BASE_LIMIT = 24;
44
+ const TOPIC_LIMIT = 24;
45
+ const TOPIC_MAX_CHARS = 96;
46
+
47
+ const inspectorQuery = z
48
+ .object({
49
+ attemptId: z.string().uuid().optional(),
50
+ cursor: z.string().min(1).max(COMPANY_BRAIN_INSPECTOR_CURSOR_MAX_CHARS).optional(),
51
+ limit: z.coerce
52
+ .number()
53
+ .int()
54
+ .positive()
55
+ .max(COMPANY_BRAIN_INSPECTOR_MAX_LIMIT)
56
+ .default(COMPANY_BRAIN_INSPECTOR_DEFAULT_LIMIT),
57
+ })
58
+ .strict()
59
+ .refine((value) => !(value.attemptId && value.cursor), {
60
+ message: "attemptId cannot be combined with cursor",
61
+ });
62
+
63
+ type ReceiptCursor = { createdAt: string; id: string };
64
+
65
+ function receiptCursorScope(workspaceId: string, subjectId: string): string {
66
+ return createHash("sha256")
67
+ .update("opengeni:company-brain-context-receipts:v1\0")
68
+ .update(workspaceId)
69
+ .update("\0")
70
+ .update(subjectId)
71
+ .digest("hex");
72
+ }
73
+
74
+ function encodeReceiptCursor(
75
+ workspaceId: string,
76
+ subjectId: string,
77
+ cursor: ReceiptCursor,
78
+ ): string {
79
+ return Buffer.from(
80
+ JSON.stringify({ v: 1, s: receiptCursorScope(workspaceId, subjectId), ...cursor }),
81
+ "utf8",
82
+ ).toString("base64url");
83
+ }
84
+
85
+ function decodeReceiptCursor(value: string, workspaceId: string, subjectId: string): ReceiptCursor {
86
+ try {
87
+ const bytes = Buffer.from(value, "base64url");
88
+ if (bytes.toString("base64url") !== value) throw new Error("encoding");
89
+ const decoded = z
90
+ .object({
91
+ v: z.literal(1),
92
+ s: z.string().regex(/^[0-9a-f]{64}$/u),
93
+ createdAt: z.string().datetime(),
94
+ id: z.string().uuid(),
95
+ })
96
+ .strict()
97
+ .parse(JSON.parse(bytes.toString("utf8")));
98
+ if (decoded.s !== receiptCursorScope(workspaceId, subjectId)) {
99
+ throw new HTTPException(409, { message: "context receipt cursor belongs to another scope" });
100
+ }
101
+ return { createdAt: decoded.createdAt, id: decoded.id };
102
+ } catch (error) {
103
+ if (error instanceof HTTPException) throw error;
104
+ throw new HTTPException(400, { message: "context receipt cursor is invalid" });
105
+ }
106
+ }
107
+
108
+ async function parsedJson<T>(
109
+ context: { req: { json: () => Promise<unknown> } },
110
+ schema: z.ZodType<T>,
111
+ ) {
112
+ const result = schema.safeParse(await context.req.json().catch(() => null));
113
+ if (!result.success) throw new HTTPException(400, { message: "invalid Company Brain request" });
114
+ return result.data;
115
+ }
116
+
117
+ function knowledgeHttpError(error: unknown): never {
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ if (message.includes("belongs to a different scope")) {
120
+ throw new HTTPException(409, { message });
121
+ }
122
+ if (message.includes("invalid knowledge") || message.includes("knowledge browse")) {
123
+ throw new HTTPException(400, { message });
124
+ }
125
+ throw error;
126
+ }
127
+
128
+ async function readCompanyBrainPackage(
129
+ deps: ApiRouteDeps,
130
+ input: {
131
+ workspaceId: string;
132
+ accountId: string;
133
+ subjectId: string;
134
+ canInspectKnowledge: boolean;
135
+ },
136
+ ) {
137
+ const generatedAt = new Date().toISOString();
138
+ const [
139
+ workspace,
140
+ instructionPolicies,
141
+ companyProfile,
142
+ preferences,
143
+ currentPreferences,
144
+ knowledge,
145
+ ] = await Promise.all([
146
+ getWorkspace(deps.db, input.workspaceId),
147
+ listWorkspaceInstructionPolicyRevisions(deps.db, input.workspaceId, { limit: 100 }),
148
+ listCompanyProfile(deps.db, {
149
+ accountId: input.accountId,
150
+ workspaceId: input.workspaceId,
151
+ limit: 100,
152
+ }),
153
+ listCompanyBrainPreferenceGuidance(deps.db, {
154
+ workspaceId: input.workspaceId,
155
+ subjectId: input.subjectId,
156
+ }),
157
+ getCurrentPreferenceRegistryGovernanceMetadata(deps.db, {
158
+ workspaceId: input.workspaceId,
159
+ subjectId: input.subjectId,
160
+ }),
161
+ input.canInspectKnowledge
162
+ ? Promise.all([
163
+ getDocumentInventory(deps.db, input.workspaceId, {
164
+ baseLimit: BASE_LIMIT,
165
+ topicLimit: TOPIC_LIMIT,
166
+ topicMaxChars: TOPIC_MAX_CHARS,
167
+ access: { viewerSubjectId: input.subjectId },
168
+ }),
169
+ listWorkspaceStateMemoryRecords(deps.db, input.workspaceId),
170
+ ]).then(([documents, memories]) => ({ documents, memories }))
171
+ : Promise.resolve(null),
172
+ ]);
173
+ if (!workspace) throw new HTTPException(404, { message: "workspace not found" });
174
+
175
+ const listedRevisionIds = new Set(instructionPolicies.revisions.map((revision) => revision.id));
176
+ const missingActiveIds = instructionPolicies.activeHeads
177
+ .map((head) => head.revisionId)
178
+ .filter((revisionId) => !listedRevisionIds.has(revisionId));
179
+ const activeInstructionPolicyRevisions: WorkspaceInstructionPolicyRevision[] = [
180
+ ...instructionPolicies.revisions.filter((revision) =>
181
+ instructionPolicies.activeHeads.some((head) => head.revisionId === revision.id),
182
+ ),
183
+ ...(await Promise.all(
184
+ missingActiveIds.map((revisionId) =>
185
+ getWorkspaceInstructionPolicyRevision(deps.db, input.workspaceId, revisionId),
186
+ ),
187
+ )),
188
+ ];
189
+ const activatedInstructionPolicyRevisionIds = await listActivatedCompanyBrainPolicyRevisionIds(
190
+ deps.db,
191
+ {
192
+ workspaceId: input.workspaceId,
193
+ subjectId: input.subjectId,
194
+ revisionIds: [...instructionPolicies.revisions, ...activeInstructionPolicyRevisions].map(
195
+ (revision) => revision.id,
196
+ ),
197
+ },
198
+ );
199
+
200
+ const state = projectWorkspaceState({
201
+ workspaceId: input.workspaceId,
202
+ generatedAt,
203
+ workspaceAgentInstructions: workspace.agentInstructions,
204
+ policies: instructionPolicies,
205
+ preferences: currentPreferences,
206
+ knowledge,
207
+ attemptGovernance: null,
208
+ });
209
+ return createCompanyBrainOkfPackage({
210
+ workspaceId: input.workspaceId,
211
+ generatedAt,
212
+ companyProfile,
213
+ instructionPolicies,
214
+ activeInstructionPolicyRevisions,
215
+ activatedInstructionPolicyRevisionIds,
216
+ preferences,
217
+ knowledge: state.knowledge,
218
+ });
219
+ }
220
+
221
+ export function registerCompanyBrainRoutes(app: Hono, deps: ApiRouteDeps): void {
222
+ const base = "/v1/workspaces/:workspaceId/company-brain";
223
+
224
+ app.get(base, async (context) => {
225
+ const workspaceId = context.req.param("workspaceId");
226
+ const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
227
+ const result = await readCompanyBrainPackage(deps, {
228
+ workspaceId,
229
+ accountId: grant.accountId,
230
+ subjectId: grant.subjectId,
231
+ canInspectKnowledge: hasPermission(grant.permissions, "documents:search"),
232
+ });
233
+ context.header("cache-control", "private, no-store");
234
+ return context.json(CompanyBrainOkfPackage.parse(result));
235
+ });
236
+
237
+ app.get(`${base}/export`, async (context) => {
238
+ const workspaceId = context.req.param("workspaceId");
239
+ const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
240
+ const result = await readCompanyBrainPackage(deps, {
241
+ workspaceId,
242
+ accountId: grant.accountId,
243
+ subjectId: grant.subjectId,
244
+ canInspectKnowledge: hasPermission(grant.permissions, "documents:search"),
245
+ });
246
+ context.header("cache-control", "private, no-store");
247
+ context.header("content-type", "text/markdown; charset=utf-8");
248
+ context.header(
249
+ "content-disposition",
250
+ `attachment; filename="company-brain-${workspaceId}.okf.md"`,
251
+ );
252
+ return context.body(serializeCompanyBrainOkf(result));
253
+ });
254
+
255
+ app.post(`${base}/knowledge/search`, async (context) => {
256
+ const workspaceId = context.req.param("workspaceId");
257
+ const grant = await requireAccessGrant(context, deps, workspaceId, "documents:search");
258
+ const request = await parsedJson(context, KnowledgeSearchRequest);
259
+ try {
260
+ const response = await searchEffectiveKnowledge(
261
+ deps.db,
262
+ {
263
+ accountId: grant.accountId,
264
+ workspaceId,
265
+ initiatingSubjectId: grant.subjectId,
266
+ surface: "human",
267
+ ...request,
268
+ },
269
+ deps.getDocumentServices(),
270
+ );
271
+ context.header("cache-control", "private, no-store");
272
+ return context.json(KnowledgeSearchResponse.parse(response));
273
+ } catch (error) {
274
+ return knowledgeHttpError(error);
275
+ }
276
+ });
277
+
278
+ app.get(`${base}/knowledge/record`, async (context) => {
279
+ const workspaceId = context.req.param("workspaceId");
280
+ const grant = await requireAccessGrant(context, deps, workspaceId, "documents:search");
281
+ const parsedId = KnowledgeRecordId.safeParse(context.req.query("id"));
282
+ if (!parsedId.success)
283
+ throw new HTTPException(400, { message: "knowledge record id is invalid" });
284
+ const record = await getEffectiveKnowledgeRecord(deps.db, {
285
+ accountId: grant.accountId,
286
+ workspaceId,
287
+ initiatingSubjectId: grant.subjectId,
288
+ surface: "human",
289
+ id: parsedId.data,
290
+ });
291
+ if (!record) throw new HTTPException(404, { message: "knowledge record not found" });
292
+ context.header("cache-control", "private, no-store");
293
+ return context.json(KnowledgeGetResponse.parse({ record }));
294
+ });
295
+
296
+ app.post(`${base}/knowledge/browse`, async (context) => {
297
+ const workspaceId = context.req.param("workspaceId");
298
+ const grant = await requireAccessGrant(context, deps, workspaceId, "documents:search");
299
+ const request = await parsedJson(context, KnowledgeBrowseRequest);
300
+ try {
301
+ const response = await browseEffectiveKnowledge(deps.db, {
302
+ accountId: grant.accountId,
303
+ workspaceId,
304
+ initiatingSubjectId: grant.subjectId,
305
+ surface: "human",
306
+ ...request,
307
+ });
308
+ context.header("cache-control", "private, no-store");
309
+ return context.json(KnowledgeBrowseResponse.parse(response));
310
+ } catch (error) {
311
+ return knowledgeHttpError(error);
312
+ }
313
+ });
314
+
315
+ app.get(`${base}/context-receipts`, async (context) => {
316
+ const workspaceId = context.req.param("workspaceId");
317
+ const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
318
+ const parsed = inspectorQuery.safeParse(context.req.query());
319
+ if (!parsed.success) throw new HTTPException(400, { message: "invalid context receipt query" });
320
+ const before = parsed.data.cursor
321
+ ? decodeReceiptCursor(parsed.data.cursor, workspaceId, grant.subjectId)
322
+ : undefined;
323
+ const rows = await inspectCompanyBrainContextReceipts(deps.db, {
324
+ workspaceId,
325
+ subjectId: grant.subjectId,
326
+ ...(parsed.data.attemptId ? { attemptId: parsed.data.attemptId } : {}),
327
+ ...(before ? { before } : {}),
328
+ limit: parsed.data.attemptId ? 1 : parsed.data.limit + 1,
329
+ });
330
+ const hasMore = !parsed.data.attemptId && rows.length > parsed.data.limit;
331
+ const receipts = rows.slice(0, parsed.data.limit);
332
+ const tail = receipts.at(-1);
333
+ const response = CompanyBrainContextReceiptPage.parse({
334
+ receipts,
335
+ hasMore,
336
+ nextCursor:
337
+ hasMore && tail
338
+ ? encodeReceiptCursor(workspaceId, grant.subjectId, {
339
+ createdAt: tail.createdAt,
340
+ id: tail.id,
341
+ })
342
+ : null,
343
+ });
344
+ context.header("cache-control", "private, no-store");
345
+ return context.json(response);
346
+ });
347
+
348
+ app.get(`${base}/knowledge-proposals`, async (context) => {
349
+ const workspaceId = context.req.param("workspaceId");
350
+ const grant = await requireAccessGrant(context, deps, workspaceId, "documents:search");
351
+ const parsed = z.coerce
352
+ .number()
353
+ .int()
354
+ .positive()
355
+ .max(COMPANY_BRAIN_INSPECTOR_MAX_LIMIT)
356
+ .default(COMPANY_BRAIN_INSPECTOR_DEFAULT_LIMIT)
357
+ .safeParse(context.req.query("limit"));
358
+ if (!parsed.success) throw new HTTPException(400, { message: "invalid proposal limit" });
359
+ const response = await listCompanyBrainKnowledgeProposals(deps.db, {
360
+ workspaceId,
361
+ subjectId: grant.subjectId,
362
+ limit: parsed.data,
363
+ });
364
+ context.header("cache-control", "private, no-store");
365
+ return context.json(CompanyBrainKnowledgeProposalPage.parse(response));
366
+ });
367
+ }
@@ -42,7 +42,7 @@ async function parseBody<S extends z.ZodType>(context: Context, schema: S): Prom
42
42
  return parsed.data;
43
43
  }
44
44
 
45
- function requireDirectAccountAdmin(access: AccessGrantAuthorization): void {
45
+ export function requireDirectAccountAdmin(access: AccessGrantAuthorization): void {
46
46
  const { grant } = access;
47
47
  if (
48
48
  !access.contextIntegrity ||