@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.
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth.d.ts +6 -0
- package/dist/{chunk-2PQMSRCB.js → chunk-QKDFBBUE.js} +29110 -18137
- package/dist/chunk-QKDFBBUE.js.map +1 -0
- package/dist/codemode.d.ts +8 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-authority-owner.d.ts +59 -0
- package/dist/connection-ownership.d.ts +47 -0
- package/dist/controller-data-plane.d.ts +16 -0
- package/dist/editable-artifact-office-import.d.ts +7 -4
- package/dist/editable-artifact-websocket.d.ts +5 -15
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/http/api-error.d.ts +10 -0
- package/dist/http/cors.d.ts +5 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/sse.d.ts +2 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +364 -86
- package/dist/index.js.map +1 -1
- package/dist/integrations/atlassian.d.ts +17 -0
- package/dist/integrations/fiken.d.ts +7 -2
- package/dist/integrations/google-drive.d.ts +19 -0
- package/dist/integrations/oauth-client.d.ts +22 -3
- package/dist/integrations/oauth-profiles.d.ts +195 -0
- package/dist/integrations/personal-github-repositories.d.ts +45 -0
- package/dist/integrations/personal-github.d.ts +40 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +8 -0
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +149 -9
- package/dist/integrations/slack-interactions.d.ts +10 -4
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/social-oauth.d.ts +5 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/documents.d.ts +1 -0
- package/dist/mcp/remember.d.ts +28 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-view.d.ts +4 -4
- package/dist/mcp/server.d.ts +15 -2
- package/dist/mcp/session-view.d.ts +4 -0
- package/dist/mcp/session-wait.d.ts +137 -0
- package/dist/routes/automations.d.ts +4 -0
- package/dist/routes/billing.d.ts +5 -0
- package/dist/routes/browser-sessions.d.ts +17 -3
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/connection-authorities.d.ts +3 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/sessions.d.ts +1 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspaces.d.ts +19 -0
- package/dist/sandbox/channel-a.d.ts +11 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +2 -0
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/metrics-ingestion.d.ts +10 -20
- package/dist/sandbox/viewer.d.ts +24 -1
- package/dist/sandbox-file-artifacts.d.ts +11 -0
- package/dist/scheduled-task-deletion.d.ts +14 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/package.json +20 -18
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +160 -27
- package/src/auth/managed-auth.ts +25 -3
- package/src/codemode.ts +53 -23
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-authority-owner.ts +61 -0
- package/src/connection-ownership.ts +180 -0
- package/src/controller-data-plane.ts +47 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +77 -9
- package/src/editable-artifact-production.ts +13 -7
- package/src/editable-artifact-websocket.ts +11 -18
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/http/api-error.ts +28 -0
- package/src/http/auth.ts +4 -0
- package/src/http/cors.ts +35 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/sse.ts +112 -66
- package/src/index.ts +46 -6
- package/src/integrations/atlassian.ts +146 -35
- package/src/integrations/fiken.ts +102 -22
- package/src/integrations/google-drive.ts +416 -101
- package/src/integrations/oauth-client.ts +272 -143
- package/src/integrations/oauth-profiles.ts +477 -0
- package/src/integrations/personal-github-repositories.ts +445 -0
- package/src/integrations/personal-github.ts +705 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +121 -5
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +337 -57
- package/src/integrations/slack-interactions.ts +1274 -214
- package/src/integrations/slack-routing.ts +324 -0
- package/src/integrations/social-oauth.ts +25 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/mcp/company-brain-governed-writes.ts +262 -0
- package/src/mcp/company-profile-agent-admin.ts +205 -0
- package/src/mcp/documents.ts +37 -6
- package/src/mcp/files.ts +22 -2
- package/src/mcp/remember.ts +181 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-view.ts +1 -0
- package/src/mcp/server.ts +719 -369
- package/src/mcp/session-view.ts +54 -0
- package/src/mcp/session-wait.ts +554 -0
- package/src/model-catalog.ts +20 -12
- package/src/routes/api-integrations.ts +4 -0
- package/src/routes/api-keys.ts +1 -0
- package/src/routes/automations.ts +534 -0
- package/src/routes/billing.ts +57 -1
- package/src/routes/browser-sessions.ts +388 -98
- package/src/routes/channels.ts +17 -1
- package/src/routes/codex.ts +50 -0
- package/src/routes/company-brain.ts +367 -0
- package/src/routes/company-profile.ts +1 -1
- package/src/routes/computer-sessions.ts +320 -93
- package/src/routes/connection-authorities.ts +139 -0
- package/src/routes/connections.ts +145 -34
- package/src/routes/documents.ts +221 -3
- package/src/routes/editable-artifacts.ts +11 -3
- package/src/routes/enrollments.ts +116 -26
- package/src/routes/environments.ts +127 -55
- package/src/routes/files.ts +152 -27
- package/src/routes/install.ts +82 -28
- package/src/routes/integration-facets.ts +29 -0
- package/src/routes/interaction-resources.ts +20 -2
- package/src/routes/machines.ts +275 -13
- package/src/routes/organization-memberships.ts +717 -28
- package/src/routes/packs.ts +4 -3
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +319 -0
- package/src/routes/pr-review.ts +531 -0
- package/src/routes/rigs.ts +104 -39
- package/src/routes/scheduled-tasks.ts +19 -21
- package/src/routes/sessions.ts +777 -40
- package/src/routes/social.ts +14 -2
- package/src/routes/supergrok.ts +23 -4
- package/src/routes/transcription-recordings.ts +8 -2
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/workspace-artifacts.ts +4 -1
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-learning.ts +228 -0
- package/src/routes/workspaces.ts +59 -6
- package/src/sandbox/auth-callout.ts +38 -12
- package/src/sandbox/channel-a.ts +242 -19
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +15 -3
- package/src/sandbox/machines.ts +186 -64
- package/src/sandbox/metrics-ingestion.ts +220 -58
- package/src/sandbox/viewer.ts +243 -19
- package/src/sandbox-file-artifacts.ts +329 -0
- package/src/scheduled-task-deletion.ts +127 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/dist/chunk-2PQMSRCB.js.map +0 -1
package/src/auth/managed-auth.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
46
|
+
readonly code: `codemode_${CodemodeAuthorityFailureReason}`;
|
|
36
47
|
|
|
37
|
-
constructor() {
|
|
38
|
-
super(
|
|
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,
|
package/src/codex-realtime.ts
CHANGED
|
@@ -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
|
|
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
|
+
}
|