@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
package/src/codemode.ts CHANGED
@@ -31,12 +31,33 @@ export type CodemodeGrantAuthority = {
31
31
  subjectId: string;
32
32
  };
33
33
 
34
+ export type CodemodeAuthorityFailureReason =
35
+ | "invalid_grant"
36
+ | "inactive_attempt"
37
+ | "catalog_mismatch";
38
+
39
+ const CODEMODE_AUTHORITY_FAILURE_MESSAGES: Record<CodemodeAuthorityFailureReason, string> = {
40
+ invalid_grant: "Codemode bearer does not carry valid execution-attempt authority",
41
+ inactive_attempt: "Codemode execution attempt is no longer active",
42
+ catalog_mismatch: "Codemode tool catalog does not match the active execution attempt",
43
+ };
44
+
34
45
  export class CodemodeAuthorityError extends Error {
35
- readonly code = "codemode_authority_invalid";
46
+ readonly code: `codemode_${CodemodeAuthorityFailureReason}`;
36
47
 
37
- constructor() {
38
- super("Codemode bearer is not bound to an active execution attempt");
48
+ constructor(readonly reason: CodemodeAuthorityFailureReason) {
49
+ super(CODEMODE_AUTHORITY_FAILURE_MESSAGES[reason]);
39
50
  this.name = "CodemodeAuthorityError";
51
+ this.code = `codemode_${reason}`;
52
+ }
53
+ }
54
+
55
+ export class CodemodeCatalogNotReadyError extends Error {
56
+ readonly code = "codemode_catalog_not_ready";
57
+
58
+ constructor() {
59
+ super("Codemode tool catalog is not ready for the active execution attempt");
60
+ this.name = "CodemodeCatalogNotReadyError";
40
61
  }
41
62
  }
42
63
 
@@ -71,11 +92,29 @@ export function isCodemodeGrant(grant: AccessGrant): boolean {
71
92
  );
72
93
  }
73
94
 
95
+ export function requireMatchingCodemodeCatalog(
96
+ authority: CodemodeGrantAuthority,
97
+ catalog: AttemptToolCatalog | null,
98
+ ): AttemptToolCatalog {
99
+ if (!catalog) throw new CodemodeCatalogNotReadyError();
100
+ if (
101
+ catalog.accountId !== authority.accountId ||
102
+ catalog.workspaceId !== authority.workspaceId ||
103
+ catalog.sessionId !== authority.sessionId ||
104
+ catalog.turnId !== authority.turnId ||
105
+ catalog.attemptId !== authority.attemptId ||
106
+ catalog.executionGeneration !== authority.executionGeneration
107
+ ) {
108
+ throw new CodemodeAuthorityError("catalog_mismatch");
109
+ }
110
+ return catalog;
111
+ }
112
+
74
113
  export async function requireActiveCodemodeCatalog(
75
114
  deps: ApiRouteDeps,
76
115
  grant: AccessGrant,
77
116
  ): Promise<{ authority: CodemodeGrantAuthority; catalog: AttemptToolCatalog }> {
78
- if (!isCodemodeGrant(grant)) throw new CodemodeAuthorityError();
117
+ if (!isCodemodeGrant(grant)) throw new CodemodeAuthorityError("invalid_grant");
79
118
  const authority = codemodeAuthorityForGrant(grant)!;
80
119
  await requireSessionAuthorization(deps, grant, {
81
120
  sessionId: authority.sessionId,
@@ -94,24 +133,16 @@ export async function requireActiveCodemodeCatalog(
94
133
  active.activeAttemptId !== authority.attemptId ||
95
134
  active.executionGeneration !== authority.executionGeneration
96
135
  ) {
97
- throw new CodemodeAuthorityError();
98
- }
99
- const catalog = await getAttemptToolCatalog(deps.db, {
100
- accountId: authority.accountId,
101
- workspaceId: authority.workspaceId,
102
- attemptId: authority.attemptId,
103
- });
104
- if (
105
- !catalog ||
106
- catalog.accountId !== authority.accountId ||
107
- catalog.workspaceId !== authority.workspaceId ||
108
- catalog.sessionId !== authority.sessionId ||
109
- catalog.turnId !== authority.turnId ||
110
- catalog.attemptId !== authority.attemptId ||
111
- catalog.executionGeneration !== authority.executionGeneration
112
- ) {
113
- throw new CodemodeAuthorityError();
136
+ throw new CodemodeAuthorityError("inactive_attempt");
114
137
  }
138
+ const catalog = requireMatchingCodemodeCatalog(
139
+ authority,
140
+ await getAttemptToolCatalog(deps.db, {
141
+ accountId: authority.accountId,
142
+ workspaceId: authority.workspaceId,
143
+ attemptId: authority.attemptId,
144
+ }),
145
+ );
115
146
  return { authority, catalog };
116
147
  }
117
148
 
@@ -128,7 +159,6 @@ export async function submitAndDispatchCodemodeCall(
128
159
  ...request,
129
160
  caller: { kind: "codemode", subjectId: authority.subjectId },
130
161
  },
131
- callLimit: deps.settings.codemodeMaxCallsPerTurn,
132
162
  });
133
163
  let operation = submitted.operation;
134
164
  let dispatch: CodemodeCallSubmissionValue["dispatch"] = terminal(operation)
@@ -171,7 +201,7 @@ export async function readCodemodeOperation(
171
201
  grant: AccessGrant,
172
202
  operationId: string,
173
203
  ): Promise<CodemodeOperation | null> {
174
- if (!isCodemodeGrant(grant)) throw new CodemodeAuthorityError();
204
+ if (!isCodemodeGrant(grant)) throw new CodemodeAuthorityError("invalid_grant");
175
205
  const authority = codemodeAuthorityForGrant(grant)!;
176
206
  return await getCodemodeOperation(deps.db, {
177
207
  accountId: authority.accountId,
@@ -5,6 +5,7 @@ import {
5
5
  CodexRealtimeError,
6
6
  CodexReloginRequired,
7
7
  createCodexRealtimeCall,
8
+ fetchCodexRealtimeProviderConfig,
8
9
  selectCodexCredentialId,
9
10
  type CodexAuthHeaders,
10
11
  type CodexFetch,
@@ -295,8 +296,13 @@ export function buildSessionCodexRealtimeBroker(
295
296
  },
296
297
  tokenResolver: (credentialId) =>
297
298
  buildCodexTokenResolver(db, settings, workspaceId, credentialId),
298
- createCall: async (auth, callInput, options) =>
299
- await createCodexRealtimeCall(auth, callInput, fetchImpl, options),
299
+ createCall: async (auth, callInput, options) => {
300
+ const providerConfig = await fetchCodexRealtimeProviderConfig(auth, fetchImpl, options);
301
+ return await createCodexRealtimeCall(auth, callInput, fetchImpl, {
302
+ ...options,
303
+ providerConfig,
304
+ });
305
+ },
300
306
  },
301
307
  { ...input, sessionId },
302
308
  );
@@ -0,0 +1,340 @@
1
+ import {
2
+ COMPANY_BRAIN_GUIDANCE_MAX_CONTENT_BYTES,
3
+ COMPANY_BRAIN_GUIDANCE_MAX_ENTRIES,
4
+ CompanyBrainOkfPackage,
5
+ type CompanyBrainGuidanceEntry,
6
+ type CompanyBrainGuidanceTruncationReason,
7
+ type CompanyBrainOkfPackage as CompanyBrainOkfPackageType,
8
+ type CompanyProfileListResponse,
9
+ type WorkspaceInstructionPolicyListResponse,
10
+ type WorkspaceInstructionPolicyRevision,
11
+ type WorkspaceStateKnowledge,
12
+ } from "@opengeni/contracts";
13
+ import type { CompanyBrainPreferenceGuidancePage } from "@opengeni/db";
14
+
15
+ const OKF_MARKER = "<!-- opengeni-company-brain-okf-v1 -->";
16
+
17
+ function canonicalize(value: unknown): unknown {
18
+ if (Array.isArray(value)) return value.map(canonicalize);
19
+ if (value === null || typeof value !== "object") return value;
20
+ return Object.fromEntries(
21
+ Object.entries(value as Record<string, unknown>)
22
+ .sort(([left], [right]) => left.localeCompare(right))
23
+ .map(([key, nested]) => [key, canonicalize(nested)]),
24
+ );
25
+ }
26
+
27
+ function canonicalJson(value: unknown): string {
28
+ return `${JSON.stringify(canonicalize(value), null, 2)}\n`;
29
+ }
30
+
31
+ function companyProfileEntries(profile: CompanyProfileListResponse): {
32
+ entries: CompanyBrainGuidanceEntry[];
33
+ truncated: boolean;
34
+ } {
35
+ const activeRevisionId = profile.current?.revisionId ?? null;
36
+ const byId = new Map(profile.revisions.map((revision) => [revision.id, revision] as const));
37
+ if (profile.activeRevision) byId.set(profile.activeRevision.id, profile.activeRevision);
38
+ return {
39
+ entries: [...byId.values()].map((revision) => {
40
+ const active = revision.id === activeRevisionId;
41
+ return {
42
+ id: "company-profile",
43
+ revisionId: revision.id,
44
+ path: "company/profile",
45
+ scope: "organization",
46
+ classification: "company_profile",
47
+ title: "Company profile",
48
+ description: "Company identity, mission, products, customers, goals, and constraints.",
49
+ // Company-profile hashes cover this exact canonical field order.
50
+ content: JSON.stringify(revision.profile),
51
+ contentHash: revision.contentHash,
52
+ revision: revision.revision,
53
+ active,
54
+ lifecycle: active ? "active" : revision.intent === "proposal" ? "proposal" : "historical",
55
+ provenance: {
56
+ source: revision.provenance.source,
57
+ sourceId: revision.provenance.sourceId,
58
+ trust: revision.intent === "proposal" ? "untrusted_proposal" : "organization_managed",
59
+ },
60
+ relationships:
61
+ revision.supersedesRevisionId && byId.has(revision.supersedesRevisionId)
62
+ ? [{ type: "supersedes" as const, targetId: revision.supersedesRevisionId }]
63
+ : [],
64
+ createdAt: revision.createdAt,
65
+ } satisfies CompanyBrainGuidanceEntry;
66
+ }),
67
+ truncated: profile.nextAfterRevision !== null,
68
+ };
69
+ }
70
+
71
+ function instructionPolicyEntries(
72
+ policies: WorkspaceInstructionPolicyListResponse,
73
+ activeRevisions: WorkspaceInstructionPolicyRevision[],
74
+ activatedRevisionIds: string[],
75
+ ): { entries: CompanyBrainGuidanceEntry[]; truncated: boolean } {
76
+ const byId = new Map(policies.revisions.map((revision) => [revision.id, revision] as const));
77
+ for (const revision of activeRevisions) byId.set(revision.id, revision);
78
+ const activeIds = new Set(policies.activeHeads.map((head) => head.revisionId));
79
+ const activatedIds = new Set(activatedRevisionIds);
80
+ return {
81
+ entries: [...byId.values()].map((revision) => {
82
+ const roleSegment = revision.roleKey ?? "all";
83
+ const active = activeIds.has(revision.id);
84
+ const lifecycle = active
85
+ ? "active"
86
+ : activatedIds.has(revision.id)
87
+ ? "historical"
88
+ : revision.provenance.source === "onboarding" ||
89
+ revision.provenance.source === "knowledge_proposal"
90
+ ? "proposal"
91
+ : "inactive";
92
+ return {
93
+ id: `instruction-policy:${revision.kind}:${revision.scope}:${roleSegment}`,
94
+ revisionId: revision.id,
95
+ path: `ways-of-working/rules/${revision.kind}/${revision.scope}/${roleSegment}`,
96
+ scope: "workspace",
97
+ classification: "mandatory_rule",
98
+ title:
99
+ revision.kind === "charter"
100
+ ? "Workspace charter"
101
+ : revision.scope === "role"
102
+ ? `Role policy: ${roleSegment}`
103
+ : "Workspace policy",
104
+ description: active ? "Mandatory context included for applicable agents." : null,
105
+ content: revision.content,
106
+ contentHash: revision.contentHash,
107
+ revision: revision.revision,
108
+ active,
109
+ lifecycle,
110
+ provenance: {
111
+ source: revision.provenance.source,
112
+ sourceId: revision.provenance.sourceId,
113
+ trust:
114
+ revision.provenance.source === "human"
115
+ ? "workspace_managed"
116
+ : revision.provenance.source === "legacy_import"
117
+ ? "migrated"
118
+ : "untrusted_proposal",
119
+ },
120
+ relationships:
121
+ revision.supersedesRevisionId && byId.has(revision.supersedesRevisionId)
122
+ ? [{ type: "supersedes" as const, targetId: revision.supersedesRevisionId }]
123
+ : [],
124
+ createdAt: revision.createdAt,
125
+ } satisfies CompanyBrainGuidanceEntry;
126
+ }),
127
+ truncated: policies.nextAfterRevision !== null,
128
+ };
129
+ }
130
+
131
+ function preferenceLifecycle(
132
+ row: CompanyBrainPreferenceGuidancePage["rows"][number],
133
+ generatedAt: string,
134
+ ): CompanyBrainGuidanceEntry["lifecycle"] {
135
+ if (row.active && row.expiresAt && row.expiresAt.getTime() <= Date.parse(generatedAt)) {
136
+ return "expired";
137
+ }
138
+ if (row.active && row.status === "active") return "active";
139
+ if (row.status === "proposed") return "proposal";
140
+ if (row.status === "inactive") return "inactive";
141
+ if (row.status === "rejected") return "rejected";
142
+ if (row.status === "superseded") return "superseded";
143
+ return "historical";
144
+ }
145
+
146
+ function preferenceEntries(
147
+ page: CompanyBrainPreferenceGuidancePage,
148
+ generatedAt: string,
149
+ ): CompanyBrainGuidanceEntry[] {
150
+ const visibleRevisionIds = new Set(page.rows.map((row) => row.revisionId));
151
+ const visiblePreferenceIds = new Set(page.rows.map((row) => row.preferenceId));
152
+ return page.rows.map((row) => {
153
+ const scope =
154
+ row.scope === "organization"
155
+ ? "organization"
156
+ : row.scope === "user"
157
+ ? "personal"
158
+ : "workspace";
159
+ const lifecycle = preferenceLifecycle(row, generatedAt);
160
+ const relationships: CompanyBrainGuidanceEntry["relationships"] = [];
161
+ if (row.correctsRevisionId && visibleRevisionIds.has(row.correctsRevisionId)) {
162
+ relationships.push({ type: "corrects", targetId: row.correctsRevisionId });
163
+ }
164
+ if (row.supersededByPreferenceId && visiblePreferenceIds.has(row.supersededByPreferenceId)) {
165
+ relationships.push({
166
+ type: "superseded_by",
167
+ targetId: `preference:${row.supersededByPreferenceId}`,
168
+ });
169
+ }
170
+ return {
171
+ id: `preference:${row.preferenceId}`,
172
+ revisionId: row.revisionId,
173
+ path: `ways-of-working/guides/${scope}/${row.stableKey}`,
174
+ scope,
175
+ classification: "guide",
176
+ title: row.title,
177
+ description: row.description,
178
+ content: row.content,
179
+ contentHash: row.contentHash,
180
+ revision: row.revision,
181
+ active: lifecycle === "active",
182
+ lifecycle,
183
+ provenance: {
184
+ source: row.provenanceSource,
185
+ sourceId: row.provenanceSourceId,
186
+ trust: row.trust,
187
+ },
188
+ relationships,
189
+ createdAt: row.createdAt.toISOString(),
190
+ } satisfies CompanyBrainGuidanceEntry;
191
+ });
192
+ }
193
+
194
+ function guidanceEntryOrder(
195
+ left: CompanyBrainGuidanceEntry,
196
+ right: CompanyBrainGuidanceEntry,
197
+ ): number {
198
+ return (
199
+ left.classification.localeCompare(right.classification) ||
200
+ left.path.localeCompare(right.path) ||
201
+ right.revision - left.revision ||
202
+ left.revisionId.localeCompare(right.revisionId)
203
+ );
204
+ }
205
+
206
+ function relationshipTargetKeys(entry: CompanyBrainGuidanceEntry): string[] {
207
+ return [entry.id, entry.revisionId];
208
+ }
209
+
210
+ export function boundCompanyBrainGuidanceEntries(entries: CompanyBrainGuidanceEntry[]): {
211
+ entries: CompanyBrainGuidanceEntry[];
212
+ itemCountTruncated: boolean;
213
+ contentBytesTruncated: boolean;
214
+ } {
215
+ const prioritized = [...entries].sort(
216
+ (left, right) => Number(right.active) - Number(left.active) || guidanceEntryOrder(left, right),
217
+ );
218
+ const selected: CompanyBrainGuidanceEntry[] = [];
219
+ let contentBytes = 0;
220
+ let itemCountTruncated = false;
221
+ let contentBytesTruncated = false;
222
+ for (const entry of prioritized) {
223
+ if (selected.length >= COMPANY_BRAIN_GUIDANCE_MAX_ENTRIES) {
224
+ itemCountTruncated = true;
225
+ continue;
226
+ }
227
+ const entryBytes = new TextEncoder().encode(entry.content).byteLength;
228
+ if (contentBytes + entryBytes > COMPANY_BRAIN_GUIDANCE_MAX_CONTENT_BYTES) {
229
+ contentBytesTruncated = true;
230
+ continue;
231
+ }
232
+ selected.push(entry);
233
+ contentBytes += entryBytes;
234
+ }
235
+ const visibleTargets = new Set(selected.flatMap(relationshipTargetKeys));
236
+ return {
237
+ entries: selected
238
+ .map((entry) => ({
239
+ ...entry,
240
+ relationships: entry.relationships.filter((relationship) =>
241
+ visibleTargets.has(relationship.targetId),
242
+ ),
243
+ }))
244
+ .sort(guidanceEntryOrder),
245
+ itemCountTruncated,
246
+ contentBytesTruncated,
247
+ };
248
+ }
249
+
250
+ export function createCompanyBrainOkfPackage(input: {
251
+ workspaceId: string;
252
+ generatedAt: string;
253
+ companyProfile: CompanyProfileListResponse;
254
+ instructionPolicies: WorkspaceInstructionPolicyListResponse;
255
+ activeInstructionPolicyRevisions: WorkspaceInstructionPolicyRevision[];
256
+ activatedInstructionPolicyRevisionIds: string[];
257
+ preferences: CompanyBrainPreferenceGuidancePage;
258
+ knowledge: WorkspaceStateKnowledge;
259
+ }): CompanyBrainOkfPackageType {
260
+ const company = companyProfileEntries(input.companyProfile);
261
+ const policies = instructionPolicyEntries(
262
+ input.instructionPolicies,
263
+ input.activeInstructionPolicyRevisions,
264
+ input.activatedInstructionPolicyRevisionIds,
265
+ );
266
+ const preferences = preferenceEntries(input.preferences, input.generatedAt);
267
+ const truncationReasons: CompanyBrainGuidanceTruncationReason[] = [];
268
+ if (company.truncated) truncationReasons.push("company_profile_history");
269
+ if (policies.truncated) truncationReasons.push("instruction_policy_history");
270
+ if (input.preferences.preferenceCountTruncated) truncationReasons.push("preference_count");
271
+ if (input.preferences.revisionCountTruncated) truncationReasons.push("preference_history");
272
+ if (input.preferences.contentBytesTruncated) truncationReasons.push("aggregate_content_bytes");
273
+
274
+ const bounded = boundCompanyBrainGuidanceEntries([
275
+ ...company.entries,
276
+ ...policies.entries,
277
+ ...preferences,
278
+ ]);
279
+ if (bounded.itemCountTruncated) truncationReasons.push("aggregate_item_count");
280
+ if (bounded.contentBytesTruncated && !truncationReasons.includes("aggregate_content_bytes")) {
281
+ truncationReasons.push("aggregate_content_bytes");
282
+ }
283
+ const knowledgeAvailable = input.knowledge.availability === "available";
284
+ return CompanyBrainOkfPackage.parse({
285
+ kind: "opengeni.company_brain.okf",
286
+ schemaVersion: 1,
287
+ workspaceId: input.workspaceId,
288
+ generatedAt: input.generatedAt,
289
+ permissions: {
290
+ guidance: "available",
291
+ knowledge: knowledgeAvailable ? "available" : "unavailable",
292
+ },
293
+ guidance: {
294
+ entries: bounded.entries,
295
+ truncated: truncationReasons.length > 0,
296
+ truncationReasons,
297
+ },
298
+ knowledge: input.knowledge,
299
+ omissions: [
300
+ ...(knowledgeAvailable ? [] : (["inaccessible_knowledge"] as const)),
301
+ "document_bodies_use_documents_export",
302
+ "memory_bodies_and_provenance",
303
+ "secret_values_and_credentials",
304
+ "session_messages_and_task_notes",
305
+ "policy_and_preference_actor_identifiers",
306
+ ],
307
+ });
308
+ }
309
+
310
+ /**
311
+ * JSON is a strict subset of YAML 1.2. Keeping the payload in one fenced YAML
312
+ * block gives humans a readable Markdown package while making arbitrary
313
+ * guidance Markdown structurally inert and exactly round-trippable.
314
+ */
315
+ export function serializeCompanyBrainOkf(value: CompanyBrainOkfPackageType): string {
316
+ const parsed = CompanyBrainOkfPackage.parse(value);
317
+ return [
318
+ OKF_MARKER,
319
+ "# OpenGeni Company Brain",
320
+ "",
321
+ "This permission-filtered package is portable evidence. Postgres remains canonical.",
322
+ "",
323
+ "```yaml",
324
+ canonicalJson(parsed).trimEnd(),
325
+ "```",
326
+ "",
327
+ ].join("\n");
328
+ }
329
+
330
+ export function parseCompanyBrainOkf(markdown: string): CompanyBrainOkfPackageType {
331
+ if (!markdown.startsWith(`${OKF_MARKER}\n`)) throw new Error("Invalid Company Brain OKF marker");
332
+ const opening = markdown.indexOf("```yaml\n");
333
+ if (opening < 0) throw new Error("Company Brain OKF payload is missing");
334
+ const payloadStart = opening + "```yaml\n".length;
335
+ const closing = markdown.indexOf("\n```", payloadStart);
336
+ if (closing < 0) throw new Error("Company Brain OKF payload is incomplete");
337
+ const trailing = markdown.slice(closing + "\n```".length).trim();
338
+ if (trailing.length > 0) throw new Error("Company Brain OKF has unexpected trailing content");
339
+ return CompanyBrainOkfPackage.parse(JSON.parse(markdown.slice(payloadStart, closing)));
340
+ }
@@ -0,0 +1,33 @@
1
+ export type ConnectedMachineComputerAccessState = {
2
+ hasDisplay: boolean;
3
+ desktopUnavailableReason: string | null;
4
+ allowScreenControl: boolean;
5
+ };
6
+
7
+ export type ConnectedMachineComputerAccessError = {
8
+ status: 403 | 409;
9
+ message: string;
10
+ };
11
+
12
+ /** Keep passive desktop viewing separate from input consent. */
13
+ export function connectedMachineComputerAccessError(
14
+ state: ConnectedMachineComputerAccessState,
15
+ requiresControl: boolean,
16
+ ): ConnectedMachineComputerAccessError | null {
17
+ if (!state.hasDisplay) {
18
+ const reason = state.desktopUnavailableReason?.trim();
19
+ return {
20
+ status: 409,
21
+ message: reason
22
+ ? `Connected Machine desktop is unavailable. ${reason}`
23
+ : "Connected Machine desktop is unavailable because this machine has no capturable display.",
24
+ };
25
+ }
26
+ if (requiresControl && !state.allowScreenControl) {
27
+ return {
28
+ status: 403,
29
+ message: "Screen control is not enabled for this Connected Machine.",
30
+ };
31
+ }
32
+ return null;
33
+ }
@@ -0,0 +1,61 @@
1
+ import type { AccessGrantAuthorization } from "@opengeni/core";
2
+ import type { UserResourceAuthoritySummary } from "@opengeni/db";
3
+ import {
4
+ IssueConnectionUseGrantRequest,
5
+ ListConnectionAuthoritiesResponse,
6
+ type IssueConnectionUseGrantRequest as IssueConnectionUseGrantRequestValue,
7
+ } from "@opengeni/contracts/connection-authority";
8
+ import { HTTPException } from "hono/http-exception";
9
+
10
+ /**
11
+ * Personal connection authority is available only to the exact authenticated
12
+ * human. API keys, delegated bearers, services, and agent attempts cannot name
13
+ * or substitute a personal owner.
14
+ */
15
+ export function requireConnectionAuthorityOwner(access: AccessGrantAuthorization): string {
16
+ if (
17
+ !access.contextIntegrity ||
18
+ access.authenticatedSubjectId !== access.grant.subjectId ||
19
+ access.grant.principalKind !== "human_session" ||
20
+ access.grant.serviceInitiator ||
21
+ access.grant.serviceInitiatorContext ||
22
+ access.grant.subjectId.startsWith("api_key:")
23
+ ) {
24
+ throw new HTTPException(403, { message: "authenticated connection owner required" });
25
+ }
26
+ return access.grant.subjectId;
27
+ }
28
+
29
+ /** Removes generic resource kind and refuses non-connection grant actions. */
30
+ export function projectSelfConnectionAuthorities(page: {
31
+ authorities: UserResourceAuthoritySummary[];
32
+ nextCursor: string | null;
33
+ }) {
34
+ return ListConnectionAuthoritiesResponse.parse({
35
+ scope: "user",
36
+ authorities: page.authorities
37
+ .filter((authority) => authority.resourceKind === "connection")
38
+ .map(({ resourceKind: _resourceKind, ...authority }) => authority),
39
+ nextCursor: page.nextCursor,
40
+ });
41
+ }
42
+
43
+ /** The API owns the action; callers supply neither action nor owner identity. */
44
+ export function connectionUseGrantLifecycleInput(input: unknown): {
45
+ action: "connection.use";
46
+ mode: IssueConnectionUseGrantRequestValue["mode"];
47
+ context: IssueConnectionUseGrantRequestValue["context"];
48
+ sessionId: string | null;
49
+ expectedAuthorityEpoch: number | null;
50
+ workspaceSharedAcknowledged: boolean;
51
+ } {
52
+ const request = IssueConnectionUseGrantRequest.parse(input);
53
+ return {
54
+ action: "connection.use",
55
+ mode: request.mode,
56
+ context: request.context,
57
+ sessionId: request.sessionId ?? null,
58
+ expectedAuthorityEpoch: request.expectedAuthorityEpoch ?? null,
59
+ workspaceSharedAcknowledged: request.workspaceSharedAcknowledged,
60
+ };
61
+ }