@opengeni/api-router 2.13.0-canary.0 → 2.13.0-canary.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.
Files changed (52) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/{chunk-BJWHVMOL.js → chunk-K2NU4KRK.js} +3165 -3662
  3. package/dist/{chunk-BJWHVMOL.js.map → chunk-K2NU4KRK.js.map} +1 -1
  4. package/dist/index.js +5 -4
  5. package/dist/index.js.map +1 -1
  6. package/dist/integrations/github-lens-connect.d.ts +2 -2
  7. package/dist/integrations/slack-interactions.d.ts +1 -1
  8. package/dist/mcp/documents.d.ts +5 -13
  9. package/dist/mcp/files.d.ts +2 -2
  10. package/dist/mcp/first-party-tool-permissions.d.ts +36 -0
  11. package/dist/mcp/knowledge-entries.d.ts +5 -0
  12. package/dist/mcp/server.d.ts +4 -4
  13. package/dist/routes/host-mcp-resolvers.d.ts +3 -0
  14. package/dist/routes/knowledge.d.ts +3 -0
  15. package/dist/sandbox-file-artifacts.d.ts +6 -2
  16. package/dist/workspace-state-projection.d.ts +3 -5
  17. package/dist/workspace-tool-gateway.d.ts +2 -1
  18. package/package.json +19 -19
  19. package/src/access-grant-rls.ts +13 -1
  20. package/src/app.ts +4 -11
  21. package/src/codemode.ts +4 -8
  22. package/src/index.ts +4 -3
  23. package/src/integrations/atlassian.ts +31 -28
  24. package/src/integrations/google-drive.ts +33 -30
  25. package/src/integrations/marketplace-plugin.ts +1 -1
  26. package/src/integrations/provider-oauth.ts +11 -2
  27. package/src/mcp/documents.ts +57 -428
  28. package/src/mcp/files.ts +76 -58
  29. package/src/mcp/first-party-tool-permissions.ts +9 -0
  30. package/src/mcp/knowledge-entries.ts +276 -0
  31. package/src/mcp/server.ts +7 -361
  32. package/src/routes/company-brain.ts +58 -107
  33. package/src/routes/documents.ts +78 -294
  34. package/src/routes/files.ts +68 -3
  35. package/src/routes/host-mcp-resolvers.ts +41 -0
  36. package/src/routes/knowledge.ts +386 -0
  37. package/src/routes/organization-memberships.ts +33 -0
  38. package/src/routes/scheduled-tasks.ts +9 -1
  39. package/src/routes/sessions.ts +20 -3
  40. package/src/routes/skill-content.ts +8 -2
  41. package/src/routes/video-generation.ts +7 -5
  42. package/src/routes/workspace-learning.ts +12 -82
  43. package/src/routes/workspace-state.ts +34 -25
  44. package/src/routes/workspaces.ts +20 -10
  45. package/src/sandbox-file-artifacts.ts +54 -2
  46. package/src/scheduled-task-deletion.ts +3 -2
  47. package/src/workspace-state-projection.ts +18 -148
  48. package/src/workspace-tool-gateway.ts +14 -25
  49. package/dist/mcp/company-brain-governed-writes.d.ts +0 -26
  50. package/dist/mcp/remember.d.ts +0 -28
  51. package/src/mcp/company-brain-governed-writes.ts +0 -271
  52. package/src/mcp/remember.ts +0 -191
package/src/mcp/server.ts CHANGED
@@ -34,7 +34,6 @@ import {
34
34
  stableJson,
35
35
  compactSessionEventResult,
36
36
  sessionEventLatestClassToSemanticClass,
37
- MemorySlackPublicationDistribution,
38
37
  SessionMcpCredentialUpdateInput,
39
38
  ToolAuthNeededPayload,
40
39
  VariableSetVariableName,
@@ -116,13 +115,8 @@ import {
116
115
  readVariableSetSecretAtomically,
117
116
  recordSyncedSocialPosts,
118
117
  listVariableSets,
119
- MEMORY_CORRECT_TOOL_DESCRIPTION,
120
- MEMORY_SAVE_TOOL_DESCRIPTION,
121
- MEMORY_SEARCH_TOOL_DESCRIPTION,
122
118
  requireScheduledTask,
123
119
  requireSession,
124
- searchWorkspaceMemories,
125
- memoryWriteScopeForAgentScope,
126
120
  type MemoryAgentScope,
127
121
  serializeEffectiveSessionControl,
128
122
  setSessionGoalStatusWithEvent,
@@ -150,11 +144,7 @@ import {
150
144
  acceptSessionHumanInputResponse,
151
145
  HumanInputResponseValidationError,
152
146
  } from "@opengeni/db";
153
- import {
154
- appendAndPublishEvents,
155
- appendAndPublishTurnEventsFenced,
156
- publishDurableSessionEvents,
157
- } from "@opengeni/events";
147
+ import { appendAndPublishTurnEventsFenced, publishDurableSessionEvents } from "@opengeni/events";
158
148
  import { allowedFirstPartyMcpToolsForSession, codemodeWorkspaceUrl } from "@opengeni/config";
159
149
  import {
160
150
  createSignedState,
@@ -170,6 +160,7 @@ import type { AnySchema, ZodRawShapeCompat } from "@modelcontextprotocol/sdk/ser
170
160
  import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
171
161
  import { HTTPException } from "hono/http-exception";
172
162
  import * as z4 from "zod/v4";
163
+ import { registerKnowledgeEntryTools } from "./knowledge-entries";
173
164
  import {
174
165
  FIRST_PARTY_TOOL_AUTHORIZATION,
175
166
  type FirstPartyToolAuthorization,
@@ -180,7 +171,6 @@ import {
180
171
  authorizedSocialConnectionsForGrant,
181
172
  authorizedAtlassianConnectionsForGrant,
182
173
  buildCapabilityCatalog,
183
- correctWorkspaceMemoryWithSlackPublication,
184
174
  nativeConnectionCapabilityRecommendations,
185
175
  requireLiveAgentAttemptAuthorization,
186
176
  requireSessionAuthorization,
@@ -188,7 +178,6 @@ import {
188
178
  resolveWorkspaceCatalogSettings,
189
179
  SessionAuthorizationDeniedError,
190
180
  SessionAuthorizationUnavailableError,
191
- saveWorkspaceMemoryWithSlackPublication,
192
181
  searchCapabilityCatalogItems,
193
182
  type ResolvedSessionAuthorization,
194
183
  } from "@opengeni/core";
@@ -318,9 +307,7 @@ import {
318
307
  } from "../integrations/atlassian";
319
308
  import { AtlassianConnectionMetadata } from "@opengeni/contracts/atlassian";
320
309
  import { registerEditableArtifactAgentTools } from "./editable-artifacts";
321
- import { registerCompanyBrainGovernedWriteTools } from "./company-brain-governed-writes";
322
310
  import { registerCompanyProfileAgentAdminTools } from "./company-profile-agent-admin";
323
- import { registerRememberTools } from "./remember";
324
311
  import { mintSandboxCodemodeToken } from "@opengeni/runtime/sandbox";
325
312
  import { deleteScheduledTaskWithDurableCleanup } from "../scheduled-task-deletion";
326
313
  import { observeWorkDiscovery, summarizeWorkDiscoveryRows } from "../work-discovery-observability";
@@ -671,22 +658,10 @@ export function buildOpenGeniMcpServer(
671
658
  if (sessionId !== null) {
672
659
  registerGoalTools(server, deps, grant, sessionId, json);
673
660
  }
674
- // A session frozen with memoryScope "off" receives no Memory tools at all,
675
- // independent of the workspace-level Memory setting.
676
- if (
677
- sessionId !== null &&
678
- options.workspaceMemoryEnabled === true &&
679
- options.sessionMemory?.mode !== "off"
680
- ) {
681
- registerMemoryTools(
682
- server,
683
- deps,
684
- grant,
685
- sessionId,
686
- json,
687
- options.workspaceMemoryPromptMode ?? "retrieval_only",
688
- options.sessionMemory ?? null,
689
- );
661
+ // Agent learning controls authoring at the accepted-attempt write boundary.
662
+ // Reading existing Knowledge and Skills remains available when authoring is Off.
663
+ if (sessionId !== null) {
664
+ registerKnowledgeEntryTools(server, deps, grant, sessionId);
690
665
  }
691
666
  server.registerTool(
692
667
  "artifacts_list",
@@ -713,19 +688,7 @@ export function buildOpenGeniMcpServer(
713
688
  registerWorkClaimTools(server, deps, grant, sessionId, json);
714
689
  }
715
690
  const attempt = exactAgentAttemptClaims(grant)!;
716
- registerCompanyBrainGovernedWriteTools({
717
- server,
718
- db: deps.db,
719
- attempt: {
720
- accountId: grant.accountId,
721
- workspaceId: grant.workspaceId,
722
- ...attempt,
723
- },
724
- authorize: async () => {
725
- await authorizeFirstPartySession(deps, grant, sessionId, "session.first_party_mcp.call");
726
- },
727
- json,
728
- });
691
+
729
692
  registerCompanyProfileAgentAdminTools({
730
693
  server,
731
694
  db: deps.db,
@@ -740,19 +703,6 @@ export function buildOpenGeniMcpServer(
740
703
  },
741
704
  json,
742
705
  });
743
- registerRememberTools({
744
- server,
745
- db: deps.db,
746
- attempt: {
747
- accountId: grant.accountId,
748
- workspaceId: grant.workspaceId,
749
- ...attempt,
750
- },
751
- authorize: async () => {
752
- await authorizeFirstPartySession(deps, grant, sessionId, "session.first_party_mcp.call");
753
- },
754
- json,
755
- });
756
706
  }
757
707
  if (sessionId !== null && exactAgentAttemptClaims(grant) !== null) {
758
708
  registerWorkspaceArtifactTools(server, deps, grant, sessionId, json);
@@ -3544,9 +3494,6 @@ function registerPreferenceRegistryTools(
3544
3494
  );
3545
3495
  }
3546
3496
 
3547
- const MemoryKindSchema = z4.enum(["preference", "semantic", "procedural", "decision", "episodic"]);
3548
- const MemoryWriteKindSchema = z4.enum(["semantic", "decision", "episodic"]);
3549
-
3550
3497
  function scheduledTaskReceipt(
3551
3498
  operation: string,
3552
3499
  task: ScheduledTask,
@@ -3637,307 +3584,6 @@ export function memorySlackPublicationActor(
3637
3584
  };
3638
3585
  }
3639
3586
 
3640
- function memoryPreview(text: string): string {
3641
- const normalized = text.replace(/\s+/g, " ").trim();
3642
- return normalized.length <= 120 ? normalized : `${normalized.slice(0, 119)}…`;
3643
- }
3644
-
3645
- function registerMemoryTools(
3646
- server: McpServer,
3647
- deps: ApiRouteDeps,
3648
- grant: AccessGrant,
3649
- sessionId: string,
3650
- json: JsonResult,
3651
- promptMode: WorkspaceMemoryPromptMode,
3652
- sessionMemory: MemoryAgentScope | null,
3653
- ): void {
3654
- // Read layers: workspace plus the session's private layer. Write layer: the
3655
- // session's narrowest layer. A null scope is the pre-0426 workspace path.
3656
- const agentScope: MemoryAgentScope = sessionMemory ?? {
3657
- mode: "workspace",
3658
- userSubjectId: null,
3659
- rootSessionId: null,
3660
- };
3661
- const writeScope = memoryWriteScopeForAgentScope(agentScope);
3662
- if (!writeScope) return;
3663
- const publicationInputSchema = z4.object({
3664
- importance: z4.enum(["major", "normal", "minor"]),
3665
- audience: z4.literal("workspace"),
3666
- slackMode: z4.enum(["auto", "review", "never"]),
3667
- shareSummary: z4.string().trim().min(1).max(4_096),
3668
- });
3669
-
3670
- server.registerTool(
3671
- "memory_search",
3672
- {
3673
- description: `${MEMORY_SEARCH_TOOL_DESCRIPTION} All existing Memory kinds are searchable. Legacy preference and procedure records are historical context, not active instructions; Skills and workspace instructions remain the behavioral authorities. When workspace Memory is enabled, use memory_save autonomously for durable facts, decisions, incidents, fixes, and outcomes, and memory_correct when an existing record is wrong or outdated.`,
3674
- inputSchema: {
3675
- query: z4.string().min(1),
3676
- kind: MemoryKindSchema.optional(),
3677
- limit: z4.number().int().positive().max(20).optional(),
3678
- },
3679
- },
3680
- async ({ query, kind, limit }) =>
3681
- json({
3682
- results: await searchWorkspaceMemories(
3683
- deps.db,
3684
- grant.workspaceId,
3685
- {
3686
- query,
3687
- ...(kind ? { kind } : {}),
3688
- ...(limit ? { limit } : {}),
3689
- agentPromptMode: promptMode,
3690
- agentScope,
3691
- },
3692
- deps.getDocumentServices().embedder,
3693
- ),
3694
- }),
3695
- );
3696
-
3697
- // Memory writes are agent-only. Human creation and curation use the REST/UI
3698
- // surface; a non-attempt MCP principal may search but cannot mutate Memory.
3699
- if (exactAgentAttemptClaims(grant) === null) return;
3700
-
3701
- server.registerTool(
3702
- "memory_save",
3703
- {
3704
- description: MEMORY_SAVE_TOOL_DESCRIPTION,
3705
- inputSchema: {
3706
- text: z4.string().min(1),
3707
- kind: MemoryWriteKindSchema,
3708
- confidence: z4.number().min(0).max(1).optional(),
3709
- replaces_id: z4.string().min(1).optional(),
3710
- slack_publication: publicationInputSchema.optional(),
3711
- },
3712
- },
3713
- async ({ text, kind, confidence, replaces_id, slack_publication }) => {
3714
- const actor = await requireLiveAgentAttemptAuthorization(deps.db, grant, sessionId);
3715
- const result = await saveWorkspaceMemoryWithSlackPublication(
3716
- deps.db,
3717
- {
3718
- accountId: grant.accountId,
3719
- workspaceId: grant.workspaceId,
3720
- sessionId,
3721
- text,
3722
- kind,
3723
- ...(confidence !== undefined ? { confidence } : {}),
3724
- ...(replaces_id ? { replacesId: replaces_id } : {}),
3725
- origin: "agent",
3726
- scope: writeScope,
3727
- },
3728
- slack_publication
3729
- ? {
3730
- distribution: MemorySlackPublicationDistribution.parse(slack_publication),
3731
- actor: memorySlackPublicationActor(actor, sessionId, grant.subjectLabel ?? null)
3732
- .actor,
3733
- ownerLabel: actor.initiator.label ?? grant.subjectLabel ?? null,
3734
- }
3735
- : null,
3736
- deps.getDocumentServices().embedder,
3737
- );
3738
- let timelineWarning: string | null = null;
3739
- try {
3740
- await appendAndPublishEvents(deps.db, deps.bus, grant.workspaceId, sessionId, [
3741
- {
3742
- type: "memory.saved",
3743
- payload: {
3744
- memoryId: result.memory.id,
3745
- kind: result.memory.kind,
3746
- preview: memoryPreview(result.memory.text),
3747
- deduped: result.deduped,
3748
- ...(result.superseded ? { supersededMemoryId: result.superseded.id } : {}),
3749
- },
3750
- },
3751
- ]);
3752
- } catch {
3753
- timelineWarning = "Memory committed, but its session timeline event could not be recorded.";
3754
- console.warn("workspace memory save: committed without session timeline event", {
3755
- errorClass: "MemoryTimelineOperationError",
3756
- errorCode: "memory_save_timeline_append_failed",
3757
- origin: "api",
3758
- workspaceId: grant.workspaceId,
3759
- sessionId,
3760
- memoryId: result.memory.id,
3761
- });
3762
- }
3763
- const changed = !result.deduped || result.updated || result.superseded !== null;
3764
- const outcome =
3765
- result.updated || result.superseded !== null
3766
- ? "updated"
3767
- : result.deduped
3768
- ? "unchanged"
3769
- : "created";
3770
- return json(
3771
- mcpMutationReceipt({
3772
- operation: "memory_save",
3773
- committed: true,
3774
- outcome,
3775
- changed,
3776
- resource: {
3777
- type: "knowledge_memory",
3778
- id: result.memory.id,
3779
- state: result.memory.status,
3780
- },
3781
- relatedResources: result.superseded
3782
- ? [
3783
- {
3784
- type: "knowledge_memory",
3785
- id: result.superseded.id,
3786
- state: result.superseded.status,
3787
- },
3788
- ]
3789
- : undefined,
3790
- timestamp: result.memory.updatedAt,
3791
- idempotency: { status: "not_supported" },
3792
- warnings: [
3793
- ...(!result.embedded
3794
- ? ["Memory committed without a vector embedding; keyword search remains available."]
3795
- : []),
3796
- ...(timelineWarning ? [timelineWarning] : []),
3797
- ],
3798
- facts: {
3799
- deduped: result.deduped,
3800
- dedupeReason: result.dedupeReason,
3801
- updatedInPlace: result.updated,
3802
- embedded: result.embedded,
3803
- slackPublicationDecision: result.slackPublication.decision?.eligible
3804
- ? "eligible"
3805
- : (result.slackPublication.decision?.reason ?? "not_requested"),
3806
- slackPublicationId:
3807
- result.slackPublication.enqueue?.kind === "enqueued" ||
3808
- result.slackPublication.enqueue?.kind === "replayed"
3809
- ? result.slackPublication.enqueue.publication.id
3810
- : null,
3811
- slackPublicationState:
3812
- result.slackPublication.enqueue?.kind === "enqueued" ||
3813
- result.slackPublication.enqueue?.kind === "replayed"
3814
- ? result.slackPublication.enqueue.publication.state
3815
- : null,
3816
- },
3817
- }),
3818
- );
3819
- },
3820
- );
3821
-
3822
- server.registerTool(
3823
- "memory_correct",
3824
- {
3825
- description: MEMORY_CORRECT_TOOL_DESCRIPTION,
3826
- inputSchema: {
3827
- id: z4.string().min(1),
3828
- reason: z4.string().min(1).optional(),
3829
- replacement_text: z4.string().min(1).optional(),
3830
- slack_publication: publicationInputSchema.optional(),
3831
- },
3832
- },
3833
- async ({ id, reason, replacement_text, slack_publication }) => {
3834
- const actor = await requireLiveAgentAttemptAuthorization(deps.db, grant, sessionId);
3835
- const result = await correctWorkspaceMemoryWithSlackPublication(
3836
- deps.db,
3837
- {
3838
- accountId: grant.accountId,
3839
- workspaceId: grant.workspaceId,
3840
- sessionId,
3841
- id,
3842
- ...(reason ? { reason } : {}),
3843
- ...(replacement_text ? { replacementText: replacement_text } : {}),
3844
- origin: "agent",
3845
- agentScope,
3846
- },
3847
- slack_publication
3848
- ? {
3849
- distribution: MemorySlackPublicationDistribution.parse(slack_publication),
3850
- actor: memorySlackPublicationActor(actor, sessionId, grant.subjectLabel ?? null)
3851
- .actor,
3852
- ownerLabel: actor.initiator.label ?? grant.subjectLabel ?? null,
3853
- }
3854
- : null,
3855
- deps.getDocumentServices().embedder,
3856
- );
3857
- let timelineWarning: string | null = null;
3858
- try {
3859
- await appendAndPublishEvents(deps.db, deps.bus, grant.workspaceId, sessionId, [
3860
- {
3861
- type: "memory.corrected",
3862
- payload: {
3863
- memoryId: result.memory.id,
3864
- kind: result.memory.kind,
3865
- preview: memoryPreview(result.memory.text),
3866
- action: result.action,
3867
- ...(reason ? { reason: memoryPreview(reason) } : {}),
3868
- ...(result.replacement
3869
- ? {
3870
- replacementMemoryId: result.replacement.id,
3871
- replacementPreview: memoryPreview(result.replacement.text),
3872
- }
3873
- : {}),
3874
- },
3875
- },
3876
- ]);
3877
- } catch {
3878
- timelineWarning =
3879
- "Memory correction committed, but its session timeline event could not be recorded.";
3880
- console.warn("workspace memory correction: committed without session timeline event", {
3881
- errorClass: "MemoryTimelineOperationError",
3882
- errorCode: "memory_correct_timeline_append_failed",
3883
- origin: "api",
3884
- workspaceId: grant.workspaceId,
3885
- sessionId,
3886
- memoryId: result.memory.id,
3887
- });
3888
- }
3889
- return json(
3890
- mcpMutationReceipt({
3891
- operation: "memory_correct",
3892
- committed: true,
3893
- outcome: "updated",
3894
- changed: true,
3895
- resource: {
3896
- type: "knowledge_memory",
3897
- id: result.memory.id,
3898
- state: result.memory.status,
3899
- },
3900
- relatedResources: result.replacement
3901
- ? [
3902
- {
3903
- type: "knowledge_memory",
3904
- id: result.replacement.id,
3905
- state: result.replacement.status,
3906
- },
3907
- ]
3908
- : undefined,
3909
- timestamp: (result.replacement ?? result.memory).updatedAt,
3910
- idempotency: { status: "not_supported" },
3911
- warnings: timelineWarning ? [timelineWarning] : [],
3912
- facts: {
3913
- correctionAction: result.action,
3914
- slackPublicationDecision: result.slackPublication.decision?.eligible
3915
- ? "eligible"
3916
- : (result.slackPublication.decision?.reason ?? "not_requested"),
3917
- slackPublicationId:
3918
- result.slackPublication.enqueue?.kind === "enqueued" ||
3919
- result.slackPublication.enqueue?.kind === "replayed"
3920
- ? result.slackPublication.enqueue.publication.id
3921
- : null,
3922
- slackPublicationState:
3923
- result.slackPublication.enqueue?.kind === "enqueued" ||
3924
- result.slackPublication.enqueue?.kind === "replayed"
3925
- ? result.slackPublication.enqueue.publication.state
3926
- : null,
3927
- },
3928
- }),
3929
- );
3930
- },
3931
- );
3932
- }
3933
-
3934
- // Fleet tools (M7 bring-your-own-compute). Session-scoped (they steer THIS
3935
- // session's active-sandbox pointer + reach the workspace's enrolled machines),
3936
- // registered only with the worker-signed sessionId claim + the selfhosted flag.
3937
- // The agent uses these to list the fleet (its Modal box + enrolled machines),
3938
- // attach/swap the active sandbox mid-conversation (heterogeneous, single-active,
3939
- // epoch-fenced), run a one-off op on a specific machine without swapping, and
3940
- // surface provisioning (enroll-a-machine) instructions to a human.
3941
3587
  function registerFleetTools(
3942
3588
  server: McpServer,
3943
3589
  deps: ApiRouteDeps,
@@ -1,3 +1,4 @@
1
+ import { withAccessGrantSessionRlsContext } from "../access-grant-rls";
1
2
  import { createHash } from "node:crypto";
2
3
  import {
3
4
  COMPANY_BRAIN_INSPECTOR_CURSOR_MAX_CHARS,
@@ -6,15 +7,17 @@ import {
6
7
  CompanyBrainContextReceiptPage,
7
8
  CompanyBrainKnowledgeProposalPage,
8
9
  CompanyBrainOkfPackage,
9
- KnowledgeBrowseRequest,
10
- KnowledgeBrowseResponse,
11
- KnowledgeGetResponse,
12
- KnowledgeRecordId,
13
- KnowledgeSearchRequest,
14
- KnowledgeSearchResponse,
10
+ WORKSPACE_STATE_KNOWLEDGE_SAMPLE_LIMIT,
15
11
  type WorkspaceInstructionPolicyRevision,
16
12
  } from "@opengeni/contracts";
17
- import { hasPermission, requireAccessGrant, type ApiRouteDeps } from "@opengeni/core";
13
+ import {
14
+ hasPermission,
15
+ requireAccessGrant,
16
+ requireAccessGrantAuthorization,
17
+ knowledgeContextForAccess,
18
+ type AccessGrantAuthorization,
19
+ type ApiRouteDeps,
20
+ } from "@opengeni/core";
18
21
  import {
19
22
  getCurrentPreferenceRegistryGovernanceMetadata,
20
23
  getWorkspace,
@@ -25,14 +28,8 @@ import {
25
28
  listCompanyBrainPreferenceGuidance,
26
29
  listCompanyProfile,
27
30
  listWorkspaceInstructionPolicyRevisions,
28
- listWorkspaceStateMemoryRecords,
31
+ listKnowledgeEntries,
29
32
  } from "@opengeni/db";
30
- import {
31
- browseEffectiveKnowledge,
32
- getDocumentInventory,
33
- getEffectiveKnowledgeRecord,
34
- searchEffectiveKnowledge,
35
- } from "@opengeni/documents";
36
33
  import type { Hono } from "hono";
37
34
  import { HTTPException } from "hono/http-exception";
38
35
  import { z } from "zod";
@@ -40,10 +37,6 @@ import { z } from "zod";
40
37
  import { createCompanyBrainOkfPackage, serializeCompanyBrainOkf } from "../company-brain-okf";
41
38
  import { projectWorkspaceState } from "../workspace-state-projection";
42
39
 
43
- const BASE_LIMIT = 24;
44
- const TOPIC_LIMIT = 24;
45
- const TOPIC_MAX_CHARS = 96;
46
-
47
40
  const inspectorQuery = z
48
41
  .object({
49
42
  attemptId: z.string().uuid().optional(),
@@ -105,26 +98,6 @@ function decodeReceiptCursor(value: string, workspaceId: string, subjectId: stri
105
98
  }
106
99
  }
107
100
 
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
101
  async function readCompanyBrainPackage(
129
102
  deps: ApiRouteDeps,
130
103
  input: {
@@ -132,6 +105,7 @@ async function readCompanyBrainPackage(
132
105
  accountId: string;
133
106
  subjectId: string;
134
107
  canInspectKnowledge: boolean;
108
+ access: AccessGrantAuthorization;
135
109
  },
136
110
  ) {
137
111
  const generatedAt = new Date().toISOString();
@@ -159,15 +133,13 @@ async function readCompanyBrainPackage(
159
133
  subjectId: input.subjectId,
160
134
  }),
161
135
  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 }))
136
+ ? withAccessGrantSessionRlsContext(deps, input.access.grant, async () =>
137
+ listKnowledgeEntries(
138
+ deps.db,
139
+ await knowledgeContextForAccess(deps, input.access, "documents:search"),
140
+ { limit: WORKSPACE_STATE_KNOWLEDGE_SAMPLE_LIMIT, view: "published" },
141
+ ),
142
+ )
171
143
  : Promise.resolve(null),
172
144
  ]);
173
145
  if (!workspace) throw new HTTPException(404, { message: "workspace not found" });
@@ -223,12 +195,19 @@ export function registerCompanyBrainRoutes(app: Hono, deps: ApiRouteDeps): void
223
195
 
224
196
  app.get(base, async (context) => {
225
197
  const workspaceId = context.req.param("workspaceId");
226
- const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
198
+ const access = await requireAccessGrantAuthorization(
199
+ context,
200
+ deps,
201
+ workspaceId,
202
+ "workspace:read",
203
+ );
204
+ const { grant } = access;
227
205
  const result = await readCompanyBrainPackage(deps, {
228
206
  workspaceId,
229
207
  accountId: grant.accountId,
230
208
  subjectId: grant.subjectId,
231
209
  canInspectKnowledge: hasPermission(grant.permissions, "documents:search"),
210
+ access,
232
211
  });
233
212
  context.header("cache-control", "private, no-store");
234
213
  return context.json(CompanyBrainOkfPackage.parse(result));
@@ -236,12 +215,19 @@ export function registerCompanyBrainRoutes(app: Hono, deps: ApiRouteDeps): void
236
215
 
237
216
  app.get(`${base}/export`, async (context) => {
238
217
  const workspaceId = context.req.param("workspaceId");
239
- const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
218
+ const access = await requireAccessGrantAuthorization(
219
+ context,
220
+ deps,
221
+ workspaceId,
222
+ "workspace:read",
223
+ );
224
+ const { grant } = access;
240
225
  const result = await readCompanyBrainPackage(deps, {
241
226
  workspaceId,
242
227
  accountId: grant.accountId,
243
228
  subjectId: grant.subjectId,
244
229
  canInspectKnowledge: hasPermission(grant.permissions, "documents:search"),
230
+ access,
245
231
  });
246
232
  context.header("cache-control", "private, no-store");
247
233
  context.header("content-type", "text/markdown; charset=utf-8");
@@ -252,69 +238,34 @@ export function registerCompanyBrainRoutes(app: Hono, deps: ApiRouteDeps): void
252
238
  return context.body(serializeCompanyBrainOkf(result));
253
239
  });
254
240
 
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(),
241
+ for (const path of [
242
+ `${base}/knowledge/search`,
243
+ `${base}/knowledge/record`,
244
+ `${base}/knowledge/browse`,
245
+ ]) {
246
+ app.all(path, async (context) => {
247
+ await requireAccessGrant(
248
+ context,
249
+ deps,
250
+ context.req.param("workspaceId")!,
251
+ "documents:search",
270
252
  );
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,
253
+ throw new HTTPException(410, {
254
+ message:
255
+ "Knowledge retrieval moved to /knowledge/entries. Use canonical entries, revisions and groups.",
307
256
  });
308
- context.header("cache-control", "private, no-store");
309
- return context.json(KnowledgeBrowseResponse.parse(response));
310
- } catch (error) {
311
- return knowledgeHttpError(error);
312
- }
313
- });
257
+ });
258
+ }
314
259
 
315
260
  app.get(`${base}/context-receipts`, async (context) => {
316
261
  const workspaceId = context.req.param("workspaceId");
317
- const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
262
+ const access = await requireAccessGrantAuthorization(
263
+ context,
264
+ deps,
265
+ workspaceId,
266
+ "workspace:read",
267
+ );
268
+ const { grant } = access;
318
269
  const parsed = inspectorQuery.safeParse(context.req.query());
319
270
  if (!parsed.success) throw new HTTPException(400, { message: "invalid context receipt query" });
320
271
  const before = parsed.data.cursor