@opengeni/api-router 0.30.1 → 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 (167) 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-2PQMSRCB.js → chunk-QKDFBBUE.js} +29110 -18137
  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-office-import.d.ts +7 -4
  13. package/dist/editable-artifact-websocket.d.ts +5 -15
  14. package/dist/editable-artifact-workspace-files.d.ts +1 -1
  15. package/dist/http/api-error.d.ts +10 -0
  16. package/dist/http/cors.d.ts +5 -0
  17. package/dist/http/interaction-control-error.d.ts +13 -0
  18. package/dist/http/sse.d.ts +2 -3
  19. package/dist/index.d.ts +5 -2
  20. package/dist/index.js +364 -86
  21. package/dist/index.js.map +1 -1
  22. package/dist/integrations/atlassian.d.ts +17 -0
  23. package/dist/integrations/fiken.d.ts +7 -2
  24. package/dist/integrations/google-drive.d.ts +19 -0
  25. package/dist/integrations/oauth-client.d.ts +22 -3
  26. package/dist/integrations/oauth-profiles.d.ts +195 -0
  27. package/dist/integrations/personal-github-repositories.d.ts +45 -0
  28. package/dist/integrations/personal-github.d.ts +40 -0
  29. package/dist/integrations/pr-review-provider.d.ts +22 -0
  30. package/dist/integrations/provider-oauth.d.ts +8 -0
  31. package/dist/integrations/slack-app-home.d.ts +24 -0
  32. package/dist/integrations/slack-bot.d.ts +149 -9
  33. package/dist/integrations/slack-interactions.d.ts +10 -4
  34. package/dist/integrations/slack-routing.d.ts +120 -0
  35. package/dist/integrations/social-oauth.d.ts +5 -0
  36. package/dist/interaction-frame-proxy.d.ts +68 -0
  37. package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
  38. package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
  39. package/dist/mcp/documents.d.ts +1 -0
  40. package/dist/mcp/remember.d.ts +28 -0
  41. package/dist/mcp/request-abort.d.ts +19 -0
  42. package/dist/mcp/scheduled-task-view.d.ts +4 -4
  43. package/dist/mcp/server.d.ts +15 -2
  44. package/dist/mcp/session-view.d.ts +4 -0
  45. package/dist/mcp/session-wait.d.ts +137 -0
  46. package/dist/routes/automations.d.ts +4 -0
  47. package/dist/routes/billing.d.ts +5 -0
  48. package/dist/routes/browser-sessions.d.ts +17 -3
  49. package/dist/routes/company-brain.d.ts +3 -0
  50. package/dist/routes/company-profile.d.ts +2 -1
  51. package/dist/routes/connection-authorities.d.ts +3 -0
  52. package/dist/routes/files.d.ts +4 -0
  53. package/dist/routes/personal-github-git-broker.d.ts +29 -0
  54. package/dist/routes/personal-github.d.ts +3 -0
  55. package/dist/routes/pr-review.d.ts +3 -0
  56. package/dist/routes/sessions.d.ts +1 -0
  57. package/dist/routes/user-resource-authorities.d.ts +3 -0
  58. package/dist/routes/workspace-capture.d.ts +10 -2
  59. package/dist/routes/workspace-learning.d.ts +3 -0
  60. package/dist/routes/workspaces.d.ts +19 -0
  61. package/dist/sandbox/channel-a.d.ts +11 -1
  62. package/dist/sandbox/connection-authority.d.ts +3 -0
  63. package/dist/sandbox/enrollment.d.ts +2 -0
  64. package/dist/sandbox/machines.d.ts +2 -2
  65. package/dist/sandbox/metrics-ingestion.d.ts +10 -20
  66. package/dist/sandbox/viewer.d.ts +24 -1
  67. package/dist/sandbox-file-artifacts.d.ts +11 -0
  68. package/dist/scheduled-task-deletion.d.ts +14 -0
  69. package/dist/slack-reaction-files.d.ts +1 -1
  70. package/dist/temporal-schedule-cleanup.d.ts +1 -0
  71. package/package.json +20 -18
  72. package/src/api-websocket.ts +23 -0
  73. package/src/app.ts +160 -27
  74. package/src/auth/managed-auth.ts +25 -3
  75. package/src/codemode.ts +53 -23
  76. package/src/codex-realtime.ts +8 -2
  77. package/src/company-brain-okf.ts +340 -0
  78. package/src/connected-machine-computer-access.ts +33 -0
  79. package/src/connection-authority-owner.ts +61 -0
  80. package/src/connection-ownership.ts +180 -0
  81. package/src/controller-data-plane.ts +47 -0
  82. package/src/editable-artifact-native-kernel.ts +26 -15
  83. package/src/editable-artifact-office-import.ts +77 -9
  84. package/src/editable-artifact-production.ts +13 -7
  85. package/src/editable-artifact-websocket.ts +11 -18
  86. package/src/editable-artifact-workspace-files.ts +31 -6
  87. package/src/http/api-error.ts +28 -0
  88. package/src/http/auth.ts +4 -0
  89. package/src/http/cors.ts +35 -0
  90. package/src/http/interaction-control-error.ts +164 -0
  91. package/src/http/sse.ts +112 -66
  92. package/src/index.ts +46 -6
  93. package/src/integrations/atlassian.ts +146 -35
  94. package/src/integrations/fiken.ts +102 -22
  95. package/src/integrations/google-drive.ts +416 -101
  96. package/src/integrations/oauth-client.ts +272 -143
  97. package/src/integrations/oauth-profiles.ts +477 -0
  98. package/src/integrations/personal-github-repositories.ts +445 -0
  99. package/src/integrations/personal-github.ts +705 -0
  100. package/src/integrations/pr-review-provider.ts +246 -0
  101. package/src/integrations/provider-oauth.ts +121 -5
  102. package/src/integrations/slack-app-home.ts +300 -0
  103. package/src/integrations/slack-bot.ts +337 -57
  104. package/src/integrations/slack-interactions.ts +1274 -214
  105. package/src/integrations/slack-routing.ts +324 -0
  106. package/src/integrations/social-oauth.ts +25 -0
  107. package/src/interaction-frame-proxy.ts +409 -0
  108. package/src/mcp/company-brain-governed-writes.ts +262 -0
  109. package/src/mcp/company-profile-agent-admin.ts +205 -0
  110. package/src/mcp/documents.ts +37 -6
  111. package/src/mcp/files.ts +22 -2
  112. package/src/mcp/remember.ts +181 -0
  113. package/src/mcp/request-abort.ts +44 -0
  114. package/src/mcp/scheduled-task-view.ts +1 -0
  115. package/src/mcp/server.ts +719 -369
  116. package/src/mcp/session-view.ts +54 -0
  117. package/src/mcp/session-wait.ts +554 -0
  118. package/src/model-catalog.ts +20 -12
  119. package/src/routes/api-integrations.ts +4 -0
  120. package/src/routes/api-keys.ts +1 -0
  121. package/src/routes/automations.ts +534 -0
  122. package/src/routes/billing.ts +57 -1
  123. package/src/routes/browser-sessions.ts +388 -98
  124. package/src/routes/channels.ts +17 -1
  125. package/src/routes/codex.ts +50 -0
  126. package/src/routes/company-brain.ts +367 -0
  127. package/src/routes/company-profile.ts +1 -1
  128. package/src/routes/computer-sessions.ts +320 -93
  129. package/src/routes/connection-authorities.ts +139 -0
  130. package/src/routes/connections.ts +145 -34
  131. package/src/routes/documents.ts +221 -3
  132. package/src/routes/editable-artifacts.ts +11 -3
  133. package/src/routes/enrollments.ts +116 -26
  134. package/src/routes/environments.ts +127 -55
  135. package/src/routes/files.ts +152 -27
  136. package/src/routes/install.ts +82 -28
  137. package/src/routes/integration-facets.ts +29 -0
  138. package/src/routes/interaction-resources.ts +20 -2
  139. package/src/routes/machines.ts +275 -13
  140. package/src/routes/organization-memberships.ts +717 -28
  141. package/src/routes/packs.ts +4 -3
  142. package/src/routes/personal-github-git-broker.ts +785 -0
  143. package/src/routes/personal-github.ts +319 -0
  144. package/src/routes/pr-review.ts +531 -0
  145. package/src/routes/rigs.ts +104 -39
  146. package/src/routes/scheduled-tasks.ts +19 -21
  147. package/src/routes/sessions.ts +777 -40
  148. package/src/routes/social.ts +14 -2
  149. package/src/routes/supergrok.ts +23 -4
  150. package/src/routes/transcription-recordings.ts +8 -2
  151. package/src/routes/user-resource-authorities.ts +138 -0
  152. package/src/routes/workspace-artifacts.ts +4 -1
  153. package/src/routes/workspace-capture.ts +21 -0
  154. package/src/routes/workspace-learning.ts +228 -0
  155. package/src/routes/workspaces.ts +59 -6
  156. package/src/sandbox/auth-callout.ts +38 -12
  157. package/src/sandbox/channel-a.ts +242 -19
  158. package/src/sandbox/connection-authority.ts +3 -0
  159. package/src/sandbox/enrollment.ts +15 -3
  160. package/src/sandbox/machines.ts +186 -64
  161. package/src/sandbox/metrics-ingestion.ts +220 -58
  162. package/src/sandbox/viewer.ts +243 -19
  163. package/src/sandbox-file-artifacts.ts +329 -0
  164. package/src/scheduled-task-deletion.ts +127 -0
  165. package/src/slack-reaction-files.ts +16 -5
  166. package/src/temporal-schedule-cleanup.ts +13 -0
  167. package/dist/chunk-2PQMSRCB.js.map +0 -1
@@ -20,10 +20,25 @@ import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-
20
20
  // importers (`app.ts`) keep the same import path.
21
21
  export type { ManagedAuth };
22
22
 
23
+ export function managedAuthRequiresEmailVerification(
24
+ settings: Pick<Settings, "environment">,
25
+ ): boolean {
26
+ return settings.environment !== "local";
27
+ }
28
+
29
+ export function managedAuthUserCreateOverride(
30
+ settings: Pick<Settings, "environment">,
31
+ user: { emailVerified: boolean } & Record<string, unknown>,
32
+ ): { data: typeof user } | undefined {
33
+ if (managedAuthRequiresEmailVerification(settings)) return undefined;
34
+ return { data: { ...user, emailVerified: true } };
35
+ }
36
+
23
37
  export function createManagedAuth(settings: Settings, db: Database): ManagedAuth | null {
24
38
  if (settings.productAccessMode !== "managed") {
25
39
  return null;
26
40
  }
41
+ const requireEmailVerification = managedAuthRequiresEmailVerification(settings);
27
42
  const pool = new Pool({ connectionString: settings.databaseUrl });
28
43
  return betterAuth({
29
44
  appName: "OpenGeni",
@@ -124,10 +139,13 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
124
139
  },
125
140
  emailAndPassword: {
126
141
  enabled: true,
127
- requireEmailVerification: true,
142
+ // Local managed mode exists so the complete human/org tenancy flow can
143
+ // be exercised without first configuring a transactional-email vendor.
144
+ // Every non-local deployment retains the verified-email boundary.
145
+ requireEmailVerification,
128
146
  revokeSessionsOnPasswordReset: true,
129
147
  onExistingUserSignUp: async ({ user }) => {
130
- if (!user.emailVerified) {
148
+ if (requireEmailVerification && !user.emailVerified) {
131
149
  const url = await verificationUrl(settings, user.email);
132
150
  await sendEmail(settings, {
133
151
  to: user.email,
@@ -147,7 +165,7 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
147
165
  },
148
166
  },
149
167
  emailVerification: {
150
- sendOnSignUp: true,
168
+ sendOnSignUp: requireEmailVerification,
151
169
  sendVerificationEmail: async ({ user, url }) => {
152
170
  await sendEmail(settings, {
153
171
  to: user.email,
@@ -161,6 +179,7 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
161
179
  userId: user.id,
162
180
  email: user.email,
163
181
  name: user.name,
182
+ emailVerified: true,
164
183
  });
165
184
  },
166
185
  },
@@ -239,11 +258,14 @@ export function createManagedAuth(settings: Settings, db: Database): ManagedAuth
239
258
  },
240
259
  user: {
241
260
  create: {
261
+ before: async (user) => managedAuthUserCreateOverride(settings, user),
242
262
  after: async (user) => {
263
+ if (!user.emailVerified) return;
243
264
  await ensureManagedAccessForUser(db, {
244
265
  userId: user.id,
245
266
  email: user.email,
246
267
  name: user.name,
268
+ emailVerified: true,
247
269
  });
248
270
  },
249
271
  },
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
+ }