@opengeni/api-router 2.3.2-canary.2 → 2.4.2-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/app.js +1 -1
- package/dist/auth/managed-auth-attempt-context.d.ts +4 -0
- package/dist/auth/managed-auth-session-adapter.d.ts +4 -0
- package/dist/{chunk-IBV7Z6F4.js → chunk-L7GVVSSQ.js} +2996 -373
- package/dist/chunk-L7GVVSSQ.js.map +1 -0
- package/dist/fatal-process-boundary.d.ts +25 -0
- package/dist/http/sse.d.ts +2 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +168 -6
- package/dist/index.js.map +1 -1
- package/dist/mcp/receipts.d.ts +9 -0
- package/dist/organization-recovery-notifications.d.ts +41 -0
- package/dist/routes/managed-auth-session-sets.d.ts +19 -0
- package/dist/routes/organization-recovery.d.ts +19 -0
- package/dist/routes/workspaces.d.ts +1 -0
- package/package.json +18 -18
- package/src/app.ts +133 -1
- package/src/auth/managed-auth-attempt-context.ts +24 -0
- package/src/auth/managed-auth-session-adapter.ts +205 -0
- package/src/auth/managed-auth.ts +52 -2
- package/src/fatal-process-boundary.ts +231 -0
- package/src/http/sse.ts +7 -0
- package/src/index.ts +25 -5
- package/src/integrations/slack-interactions.ts +30 -17
- package/src/mcp/receipts.ts +34 -0
- package/src/mcp/server.ts +45 -39
- package/src/organization-recovery-notifications.ts +103 -0
- package/src/routes/canonical-human-identities.ts +29 -14
- package/src/routes/codex.ts +5 -1
- package/src/routes/environments.ts +23 -0
- package/src/routes/interaction-resources.ts +3 -0
- package/src/routes/managed-auth-session-sets.ts +994 -0
- package/src/routes/managed-onboarding.ts +2 -0
- package/src/routes/organization-memberships.ts +2 -0
- package/src/routes/organization-recovery.ts +325 -0
- package/src/routes/sessions.ts +24 -39
- package/src/routes/supergrok.ts +5 -1
- package/src/routes/workspaces.ts +23 -1
- package/dist/chunk-IBV7Z6F4.js.map +0 -1
|
@@ -309,6 +309,8 @@ async function requireManagedHuman(context: Context, deps: ApiRouteDeps) {
|
|
|
309
309
|
}
|
|
310
310
|
const session = await getManagedSession(context, deps.managedAuth, {
|
|
311
311
|
db: deps.db,
|
|
312
|
+
sessionAdapter: deps.managedAuthSessionAdapter,
|
|
313
|
+
sessionSetMode: deps.settings.managedAuthSessionSetMode,
|
|
312
314
|
});
|
|
313
315
|
if (!session?.user) {
|
|
314
316
|
throw new HTTPException(401, { message: "managed human session required" });
|
|
@@ -91,6 +91,8 @@ async function requireManagedHuman(context: Context, deps: ApiRouteDeps) {
|
|
|
91
91
|
}
|
|
92
92
|
const session = await getManagedSession(context, deps.managedAuth, {
|
|
93
93
|
db: deps.db,
|
|
94
|
+
sessionAdapter: deps.managedAuthSessionAdapter,
|
|
95
|
+
sessionSetMode: deps.settings.managedAuthSessionSetMode,
|
|
94
96
|
});
|
|
95
97
|
if (!session?.user) {
|
|
96
98
|
throw new HTTPException(401, { message: "managed human session required" });
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AcceptOrganizationRecoveryCustodyRequest,
|
|
3
|
+
ConfigureOrganizationRecoveryPolicyRequest,
|
|
4
|
+
DisableOrganizationRecoveryPolicyRequest,
|
|
5
|
+
OrganizationRecoveryMutationResponse,
|
|
6
|
+
OrganizationRecoveryOperationCommandRequest,
|
|
7
|
+
OrganizationRecoveryOverview,
|
|
8
|
+
StartOrganizationRecoveryOperationRequest,
|
|
9
|
+
} from "@opengeni/contracts/organization-recovery";
|
|
10
|
+
import {
|
|
11
|
+
getManagedAuthRequestActorAdmissionStamp,
|
|
12
|
+
getManagedAuthRequestActorLeaseStamp,
|
|
13
|
+
requireCanonicalHumanRequestIdentity,
|
|
14
|
+
} from "@opengeni/core/canonical-human-identities";
|
|
15
|
+
import type { ApiRouteDeps } from "@opengeni/core";
|
|
16
|
+
import {
|
|
17
|
+
acceptOrganizationRecoveryCustody,
|
|
18
|
+
approveOrganizationRecoveryOperation,
|
|
19
|
+
cancelOrganizationRecoveryOperation,
|
|
20
|
+
configureOrganizationRecoveryPolicy,
|
|
21
|
+
disableOrganizationRecoveryPolicy,
|
|
22
|
+
executeOrganizationRecoveryOperation,
|
|
23
|
+
getOrganizationRecoveryOverview,
|
|
24
|
+
OrganizationRecoveryDeniedError,
|
|
25
|
+
OrganizationRecoveryOperationReuseError,
|
|
26
|
+
OrganizationRecoveryRevisionConflictError,
|
|
27
|
+
OrganizationRecoveryUnavailableError,
|
|
28
|
+
startOrganizationRecoveryOperation,
|
|
29
|
+
} from "@opengeni/db";
|
|
30
|
+
import type { Context, Hono } from "hono";
|
|
31
|
+
import { HTTPException } from "hono/http-exception";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
import { ApiHttpError } from "../http/api-error";
|
|
34
|
+
|
|
35
|
+
const OrganizationId = z.string().uuid();
|
|
36
|
+
const RecoveryOperationId = z.string().uuid();
|
|
37
|
+
|
|
38
|
+
export type OrganizationRecoveryRouteServices = {
|
|
39
|
+
requireCanonicalHumanRequestIdentity: typeof requireCanonicalHumanRequestIdentity;
|
|
40
|
+
getManagedAuthRequestActorAdmissionStamp: typeof getManagedAuthRequestActorAdmissionStamp;
|
|
41
|
+
getManagedAuthRequestActorLeaseStamp: typeof getManagedAuthRequestActorLeaseStamp;
|
|
42
|
+
getOrganizationRecoveryOverview: typeof getOrganizationRecoveryOverview;
|
|
43
|
+
configureOrganizationRecoveryPolicy: typeof configureOrganizationRecoveryPolicy;
|
|
44
|
+
acceptOrganizationRecoveryCustody: typeof acceptOrganizationRecoveryCustody;
|
|
45
|
+
disableOrganizationRecoveryPolicy: typeof disableOrganizationRecoveryPolicy;
|
|
46
|
+
startOrganizationRecoveryOperation: typeof startOrganizationRecoveryOperation;
|
|
47
|
+
approveOrganizationRecoveryOperation: typeof approveOrganizationRecoveryOperation;
|
|
48
|
+
cancelOrganizationRecoveryOperation: typeof cancelOrganizationRecoveryOperation;
|
|
49
|
+
executeOrganizationRecoveryOperation: typeof executeOrganizationRecoveryOperation;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const productionServices: OrganizationRecoveryRouteServices = {
|
|
53
|
+
requireCanonicalHumanRequestIdentity,
|
|
54
|
+
getManagedAuthRequestActorAdmissionStamp,
|
|
55
|
+
getManagedAuthRequestActorLeaseStamp,
|
|
56
|
+
getOrganizationRecoveryOverview,
|
|
57
|
+
configureOrganizationRecoveryPolicy,
|
|
58
|
+
acceptOrganizationRecoveryCustody,
|
|
59
|
+
disableOrganizationRecoveryPolicy,
|
|
60
|
+
startOrganizationRecoveryOperation,
|
|
61
|
+
approveOrganizationRecoveryOperation,
|
|
62
|
+
cancelOrganizationRecoveryOperation,
|
|
63
|
+
executeOrganizationRecoveryOperation,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
async function requireRecoveryIdentity(
|
|
67
|
+
context: Context,
|
|
68
|
+
deps: ApiRouteDeps,
|
|
69
|
+
services: OrganizationRecoveryRouteServices,
|
|
70
|
+
) {
|
|
71
|
+
// Recovery is a browser-session ceremony. Never let ambient API, bearer,
|
|
72
|
+
// service, or machine authority become a competing recovery identity.
|
|
73
|
+
if (
|
|
74
|
+
deps.settings.productAccessMode !== "managed" ||
|
|
75
|
+
!deps.managedAuth ||
|
|
76
|
+
!context.req.header("cookie") ||
|
|
77
|
+
context.req.header("authorization")
|
|
78
|
+
) {
|
|
79
|
+
throw new HTTPException(401, {
|
|
80
|
+
message: "Managed human authentication required",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const identity = await services.requireCanonicalHumanRequestIdentity(context, {
|
|
84
|
+
db: deps.db,
|
|
85
|
+
managedAuth: deps.managedAuth,
|
|
86
|
+
managedAuthSessionAdapter: deps.managedAuthSessionAdapter,
|
|
87
|
+
managedAuthSessionSetMode: deps.settings.managedAuthSessionSetMode,
|
|
88
|
+
allowRecovery: false,
|
|
89
|
+
});
|
|
90
|
+
return { ...identity, actorSubjectId: `user:${identity.authUserId}` };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function parseBody<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
|
|
94
|
+
const parsed = schema.safeParse(await context.req.json().catch(() => null));
|
|
95
|
+
if (!parsed.success) {
|
|
96
|
+
throw new HTTPException(422, {
|
|
97
|
+
message: "Invalid organization recovery request",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return parsed.data;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseId(schema: z.ZodString, value: string, label: string): string {
|
|
104
|
+
const parsed = schema.safeParse(value);
|
|
105
|
+
if (!parsed.success) throw new HTTPException(422, { message: `Invalid ${label}` });
|
|
106
|
+
return parsed.data;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function organizationId(context: Context): string {
|
|
110
|
+
return parseId(OrganizationId, context.req.param("organizationId") ?? "", "organization id");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function recoveryOperationId(context: Context): string {
|
|
114
|
+
return parseId(
|
|
115
|
+
RecoveryOperationId,
|
|
116
|
+
context.req.param("recoveryOperationId") ?? "",
|
|
117
|
+
"recovery operation id",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function organizationRecoveryHttpError(error: unknown): Error {
|
|
122
|
+
if (error instanceof OrganizationRecoveryRevisionConflictError) {
|
|
123
|
+
return new ApiHttpError(409, {
|
|
124
|
+
code: "conflict",
|
|
125
|
+
message: "Recovery state changed; refresh before submitting a new action.",
|
|
126
|
+
retryable: false,
|
|
127
|
+
outcomeUnknown: false,
|
|
128
|
+
details: { code: error.code },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (error instanceof OrganizationRecoveryOperationReuseError) {
|
|
132
|
+
return new ApiHttpError(409, {
|
|
133
|
+
code: "idempotency_conflict",
|
|
134
|
+
message: "Operation id was reused with different input.",
|
|
135
|
+
retryable: false,
|
|
136
|
+
outcomeUnknown: false,
|
|
137
|
+
details: { code: error.code },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (error instanceof OrganizationRecoveryUnavailableError) {
|
|
141
|
+
return new ApiHttpError(409, {
|
|
142
|
+
code: "conflict",
|
|
143
|
+
message: "Organization recovery is unavailable.",
|
|
144
|
+
retryable: false,
|
|
145
|
+
outcomeUnknown: false,
|
|
146
|
+
details: { code: error.code },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (error instanceof OrganizationRecoveryDeniedError) {
|
|
150
|
+
return new ApiHttpError(404, {
|
|
151
|
+
code: "not_found",
|
|
152
|
+
message: "Organization recovery not found.",
|
|
153
|
+
retryable: false,
|
|
154
|
+
outcomeUnknown: false,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (error instanceof Error) return error;
|
|
158
|
+
return new Error("Organization recovery failed");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function requireActorFence(
|
|
162
|
+
context: Context,
|
|
163
|
+
deps: ApiRouteDeps,
|
|
164
|
+
services: OrganizationRecoveryRouteServices,
|
|
165
|
+
) {
|
|
166
|
+
const actorFence = services.getManagedAuthRequestActorLeaseStamp(context.req.raw);
|
|
167
|
+
if (deps.settings.managedAuthSessionSetMode !== "legacy" && !actorFence) {
|
|
168
|
+
throw new HTTPException(409, {
|
|
169
|
+
message: "Managed actor mutation fence is unavailable",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (!actorFence) {
|
|
173
|
+
throw new HTTPException(409, {
|
|
174
|
+
message: "Organization recovery requires provider-neutral browser login slots",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return actorFence;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function mutationContext(
|
|
181
|
+
context: Context,
|
|
182
|
+
deps: ApiRouteDeps,
|
|
183
|
+
services: OrganizationRecoveryRouteServices,
|
|
184
|
+
) {
|
|
185
|
+
const identity = await requireRecoveryIdentity(context, deps, services);
|
|
186
|
+
return {
|
|
187
|
+
organizationId: organizationId(context),
|
|
188
|
+
actorSubjectId: identity.actorSubjectId,
|
|
189
|
+
actorAuthUserId: identity.authUserId,
|
|
190
|
+
actorAuthSessionId: identity.authSessionId,
|
|
191
|
+
actorFence: requireActorFence(context, deps, services),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function mutationResponse(context: Context, value: unknown): Response {
|
|
196
|
+
return context.json(OrganizationRecoveryMutationResponse.parse(value));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function registerOrganizationRecoveryRoutes(
|
|
200
|
+
app: Hono,
|
|
201
|
+
deps: ApiRouteDeps,
|
|
202
|
+
services: OrganizationRecoveryRouteServices = productionServices,
|
|
203
|
+
): void {
|
|
204
|
+
const base = "/v1/organizations/:organizationId/recovery";
|
|
205
|
+
|
|
206
|
+
app.get(base, async (context) => {
|
|
207
|
+
const identity = await requireRecoveryIdentity(context, deps, services);
|
|
208
|
+
try {
|
|
209
|
+
return context.json(
|
|
210
|
+
OrganizationRecoveryOverview.parse(
|
|
211
|
+
await services.getOrganizationRecoveryOverview(deps.db, {
|
|
212
|
+
organizationId: organizationId(context),
|
|
213
|
+
actorSubjectId: identity.actorSubjectId,
|
|
214
|
+
actorAuthUserId: identity.authUserId,
|
|
215
|
+
actorAuthSessionId: identity.authSessionId,
|
|
216
|
+
actorFence: services.getManagedAuthRequestActorAdmissionStamp(context.req.raw) ?? null,
|
|
217
|
+
}),
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
throw organizationRecoveryHttpError(error);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
app.put(`${base}/policy`, async (context) => {
|
|
226
|
+
const authority = await mutationContext(context, deps, services);
|
|
227
|
+
const payload = await parseBody(context, ConfigureOrganizationRecoveryPolicyRequest);
|
|
228
|
+
try {
|
|
229
|
+
return mutationResponse(
|
|
230
|
+
context,
|
|
231
|
+
await services.configureOrganizationRecoveryPolicy(deps.db, {
|
|
232
|
+
...authority,
|
|
233
|
+
operationId: payload.operationId,
|
|
234
|
+
expectedPolicyRevision: payload.expectedPolicyRevision,
|
|
235
|
+
custodianMembershipIds: payload.custodianMembershipIds,
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw organizationRecoveryHttpError(error);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
app.post(`${base}/policy/accept`, async (context) => {
|
|
244
|
+
const authority = await mutationContext(context, deps, services);
|
|
245
|
+
const payload = await parseBody(context, AcceptOrganizationRecoveryCustodyRequest);
|
|
246
|
+
try {
|
|
247
|
+
return mutationResponse(
|
|
248
|
+
context,
|
|
249
|
+
await services.acceptOrganizationRecoveryCustody(deps.db, {
|
|
250
|
+
...authority,
|
|
251
|
+
operationId: payload.operationId,
|
|
252
|
+
expectedPolicyRevision: payload.expectedPolicyRevision,
|
|
253
|
+
}),
|
|
254
|
+
);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
throw organizationRecoveryHttpError(error);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
app.post(`${base}/policy/disable`, async (context) => {
|
|
261
|
+
const authority = await mutationContext(context, deps, services);
|
|
262
|
+
const payload = await parseBody(context, DisableOrganizationRecoveryPolicyRequest);
|
|
263
|
+
try {
|
|
264
|
+
return mutationResponse(
|
|
265
|
+
context,
|
|
266
|
+
await services.disableOrganizationRecoveryPolicy(deps.db, {
|
|
267
|
+
...authority,
|
|
268
|
+
operationId: payload.operationId,
|
|
269
|
+
expectedPolicyRevision: payload.expectedPolicyRevision,
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
throw organizationRecoveryHttpError(error);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
app.post(`${base}/operations`, async (context) => {
|
|
278
|
+
const authority = await mutationContext(context, deps, services);
|
|
279
|
+
const payload = await parseBody(context, StartOrganizationRecoveryOperationRequest);
|
|
280
|
+
try {
|
|
281
|
+
return mutationResponse(
|
|
282
|
+
context,
|
|
283
|
+
await services.startOrganizationRecoveryOperation(deps.db, {
|
|
284
|
+
...authority,
|
|
285
|
+
operationId: payload.operationId,
|
|
286
|
+
expectedPolicyRevision: payload.expectedPolicyRevision,
|
|
287
|
+
targetMembershipId: payload.targetMembershipId,
|
|
288
|
+
}),
|
|
289
|
+
);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throw organizationRecoveryHttpError(error);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
async function mutateOperation(
|
|
296
|
+
context: Context,
|
|
297
|
+
command: typeof approveOrganizationRecoveryOperation,
|
|
298
|
+
): Promise<Response> {
|
|
299
|
+
const authority = await mutationContext(context, deps, services);
|
|
300
|
+
const payload = await parseBody(context, OrganizationRecoveryOperationCommandRequest);
|
|
301
|
+
try {
|
|
302
|
+
return mutationResponse(
|
|
303
|
+
context,
|
|
304
|
+
await command(deps.db, {
|
|
305
|
+
...authority,
|
|
306
|
+
recoveryOperationId: recoveryOperationId(context),
|
|
307
|
+
operationId: payload.operationId,
|
|
308
|
+
expectedOperationRevision: payload.expectedOperationRevision,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
throw organizationRecoveryHttpError(error);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
app.post(`${base}/operations/:recoveryOperationId/approve`, async (context) =>
|
|
317
|
+
mutateOperation(context, services.approveOrganizationRecoveryOperation),
|
|
318
|
+
);
|
|
319
|
+
app.post(`${base}/operations/:recoveryOperationId/cancel`, async (context) =>
|
|
320
|
+
mutateOperation(context, services.cancelOrganizationRecoveryOperation),
|
|
321
|
+
);
|
|
322
|
+
app.post(`${base}/operations/:recoveryOperationId/execute`, async (context) =>
|
|
323
|
+
mutateOperation(context, services.executeOrganizationRecoveryOperation),
|
|
324
|
+
);
|
|
325
|
+
}
|
package/src/routes/sessions.ts
CHANGED
|
@@ -211,6 +211,7 @@ import type { Context, Hono, MiddlewareHandler } from "hono";
|
|
|
211
211
|
import { HTTPException } from "hono/http-exception";
|
|
212
212
|
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
213
213
|
import {
|
|
214
|
+
getManagedAuthRequestActorEpoch,
|
|
214
215
|
hasPermission,
|
|
215
216
|
requireAccessGrant,
|
|
216
217
|
requireAccessGrantAuthorization,
|
|
@@ -243,7 +244,6 @@ import {
|
|
|
243
244
|
import { buildSessionCodexRealtimeBroker, CodexRealtimeBrokerError } from "../codex-realtime";
|
|
244
245
|
import {
|
|
245
246
|
acceptSessionUserMessage,
|
|
246
|
-
acceptSessionUserMessageWithOutcome,
|
|
247
247
|
controlHumanSessionWorkstream,
|
|
248
248
|
createSessionForRequest,
|
|
249
249
|
deleteHumanQueuePrompt,
|
|
@@ -259,6 +259,7 @@ import {
|
|
|
259
259
|
SessionSpawnDeniedError,
|
|
260
260
|
sessionSpawnDenialEnvelope,
|
|
261
261
|
steerHumanQueuePrompt,
|
|
262
|
+
submitComposerDraftForRequest,
|
|
262
263
|
updateSessionMcpApprovalPolicy,
|
|
263
264
|
updateManagedHumanSessionVisibility,
|
|
264
265
|
updateSessionToolPolicy,
|
|
@@ -1572,7 +1573,13 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
1572
1573
|
// while the actively-working label remains independent of acknowledgment.
|
|
1573
1574
|
app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/attention", async (c) => {
|
|
1574
1575
|
const workspaceId = c.req.param("workspaceId");
|
|
1575
|
-
const
|
|
1576
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
1577
|
+
c,
|
|
1578
|
+
deps,
|
|
1579
|
+
workspaceId,
|
|
1580
|
+
"sessions:read",
|
|
1581
|
+
);
|
|
1582
|
+
const grant = authorization.grant;
|
|
1576
1583
|
const sessionId = c.req.param("sessionId");
|
|
1577
1584
|
if (!z.string().uuid().safeParse(sessionId).success) {
|
|
1578
1585
|
throw new HTTPException(404, { message: "session not found" });
|
|
@@ -1586,6 +1593,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
1586
1593
|
workspaceId,
|
|
1587
1594
|
subjectId: grant.subjectId,
|
|
1588
1595
|
sessionId,
|
|
1596
|
+
personalWorkspaceOwnerException: authorization.canonicalManagedHumanSession,
|
|
1589
1597
|
...parsed.data,
|
|
1590
1598
|
});
|
|
1591
1599
|
if (!session) throw new HTTPException(404, { message: "session not found" });
|
|
@@ -1618,7 +1626,13 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
1618
1626
|
// for this member and remain recoverable through the archived list view.
|
|
1619
1627
|
app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/archive", async (c) => {
|
|
1620
1628
|
const workspaceId = c.req.param("workspaceId");
|
|
1621
|
-
const
|
|
1629
|
+
const authorization = await requireAccessGrantAuthorization(
|
|
1630
|
+
c,
|
|
1631
|
+
deps,
|
|
1632
|
+
workspaceId,
|
|
1633
|
+
"sessions:read",
|
|
1634
|
+
);
|
|
1635
|
+
const grant = authorization.grant;
|
|
1622
1636
|
const sessionId = c.req.param("sessionId");
|
|
1623
1637
|
const parsed = UpdateSessionArchiveRequest.safeParse(await c.req.json().catch(() => null));
|
|
1624
1638
|
if (!parsed.success) {
|
|
@@ -1629,6 +1643,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
1629
1643
|
workspaceId,
|
|
1630
1644
|
subjectId: grant.subjectId,
|
|
1631
1645
|
sessionId,
|
|
1646
|
+
personalWorkspaceOwnerException: authorization.canonicalManagedHumanSession,
|
|
1632
1647
|
...parsed.data,
|
|
1633
1648
|
});
|
|
1634
1649
|
if (!session) throw new HTTPException(404, { message: "session not found" });
|
|
@@ -2559,6 +2574,7 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
2559
2574
|
c.req.raw.signal,
|
|
2560
2575
|
{
|
|
2561
2576
|
observability: deps.observability,
|
|
2577
|
+
actorEpoch: getManagedAuthRequestActorEpoch(c.req.raw) ?? undefined,
|
|
2562
2578
|
reauthorizeAfterMs:
|
|
2563
2579
|
authorization?.reauthorizeAfterMs ?? SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
|
|
2564
2580
|
reauthorize: async () => {
|
|
@@ -2832,46 +2848,15 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
2832
2848
|
const sessionId = c.req.param("sessionId");
|
|
2833
2849
|
await assertSessionExists(db, workspaceId, sessionId);
|
|
2834
2850
|
const payload = SubmitComposerDraftRequest.parse(await c.req.json().catch(() => null));
|
|
2835
|
-
let result: Awaited<ReturnType<typeof
|
|
2851
|
+
let result: Awaited<ReturnType<typeof submitComposerDraftForRequest>>;
|
|
2836
2852
|
try {
|
|
2837
|
-
result = await
|
|
2838
|
-
text: payload.text,
|
|
2839
|
-
annotations: payload.annotations,
|
|
2840
|
-
modelContext: payload.modelContext ?? null,
|
|
2841
|
-
resources: payload.resources,
|
|
2842
|
-
model: payload.model,
|
|
2843
|
-
reasoningEffort: payload.reasoningEffort,
|
|
2844
|
-
latencyMode: payload.latencyMode,
|
|
2845
|
-
mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
|
|
2846
|
-
connectionAuthorities: payload.connectionAuthorities,
|
|
2847
|
-
...(payload.personalResourceAttachment
|
|
2848
|
-
? { personalResourceAttachment: payload.personalResourceAttachment }
|
|
2849
|
-
: {}),
|
|
2853
|
+
result = await submitComposerDraftForRequest(deps, grant, workspaceId, sessionId, payload, {
|
|
2850
2854
|
authorization,
|
|
2851
|
-
delivery: payload.delivery,
|
|
2852
|
-
origin: "human",
|
|
2853
|
-
expectedDraftRevision: payload.expectedDraftRevision,
|
|
2854
|
-
clientEventId: payload.clientEventId,
|
|
2855
|
-
...(payload.controlEtag ? { controlEtag: payload.controlEtag } : {}),
|
|
2856
2855
|
});
|
|
2857
2856
|
} catch (error) {
|
|
2858
2857
|
return commandConflictResponse(c, error);
|
|
2859
2858
|
}
|
|
2860
|
-
|
|
2861
|
-
throw new Error("Accepted composer draft submission did not return its next draft");
|
|
2862
|
-
}
|
|
2863
|
-
return c.json(
|
|
2864
|
-
{
|
|
2865
|
-
accepted: result.accepted,
|
|
2866
|
-
turn: result.turn,
|
|
2867
|
-
draft: result.draft,
|
|
2868
|
-
receipt: result.receipt,
|
|
2869
|
-
routing: result.routing,
|
|
2870
|
-
interruptionCount: result.interruptionCount,
|
|
2871
|
-
replay: result.replay,
|
|
2872
|
-
},
|
|
2873
|
-
202,
|
|
2874
|
-
);
|
|
2859
|
+
return c.json(result, 202);
|
|
2875
2860
|
});
|
|
2876
2861
|
|
|
2877
2862
|
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/events", async (c) => {
|
|
@@ -2995,10 +2980,10 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
|
|
|
2995
2980
|
}
|
|
2996
2981
|
if (accepted.action === "conflict") {
|
|
2997
2982
|
throw new HTTPException(409, {
|
|
2998
|
-
message:
|
|
2983
|
+
message: "human-input request is not currently actionable",
|
|
2999
2984
|
});
|
|
3000
2985
|
}
|
|
3001
|
-
return c.json(accepted.event, 202);
|
|
2986
|
+
return c.json(accepted.event, accepted.action === "completed" ? 200 : 202);
|
|
3002
2987
|
}
|
|
3003
2988
|
});
|
|
3004
2989
|
|
package/src/routes/supergrok.ts
CHANGED
|
@@ -80,7 +80,11 @@ async function managedCookieHuman(
|
|
|
80
80
|
) {
|
|
81
81
|
return null;
|
|
82
82
|
}
|
|
83
|
-
const session = await getManagedSession(c, deps.managedAuth
|
|
83
|
+
const session = await getManagedSession(c, deps.managedAuth, {
|
|
84
|
+
db: deps.db,
|
|
85
|
+
sessionAdapter: deps.managedAuthSessionAdapter,
|
|
86
|
+
sessionSetMode: deps.settings.managedAuthSessionSetMode,
|
|
87
|
+
});
|
|
84
88
|
return session?.user?.id ? { subjectId: `user:${session.user.id}` } : null;
|
|
85
89
|
}
|
|
86
90
|
|
package/src/routes/workspaces.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { boundWorkspaceControlHttpPage } from "@opengeni/events";
|
|
|
45
45
|
import type { Hono } from "hono";
|
|
46
46
|
import { HTTPException } from "hono/http-exception";
|
|
47
47
|
import {
|
|
48
|
+
getManagedAuthRequestActorEpoch,
|
|
48
49
|
hasPermission,
|
|
49
50
|
requireAccessContext,
|
|
50
51
|
requireAccessGrant,
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
resolveMemberSubjectId,
|
|
59
60
|
} from "@opengeni/core";
|
|
60
61
|
import { boundedLimit } from "../http/common";
|
|
62
|
+
import { ApiHttpError } from "../http/api-error";
|
|
61
63
|
import { sseWorkspaceControlStream } from "../http/sse";
|
|
62
64
|
import { buildWorkspaceModelCatalog } from "../model-catalog";
|
|
63
65
|
import { processTemporalScheduleCleanupClaims } from "../temporal-schedule-cleanup";
|
|
@@ -97,6 +99,14 @@ export function workspaceMembersResponse(members: readonly WorkspaceMemberProjec
|
|
|
97
99
|
});
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
export function workspaceUpdateRequestsAccountTransfer(value: unknown): boolean {
|
|
103
|
+
return (
|
|
104
|
+
typeof value === "object" &&
|
|
105
|
+
value !== null &&
|
|
106
|
+
Object.prototype.hasOwnProperty.call(value, "accountId")
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
101
111
|
app.get("/v1/access/me", async (c) => {
|
|
102
112
|
return c.json(await requireAccessContext(c, deps));
|
|
@@ -163,7 +173,18 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
163
173
|
app.patch("/v1/workspaces/:workspaceId", async (c) => {
|
|
164
174
|
const workspaceId = c.req.param("workspaceId");
|
|
165
175
|
await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
166
|
-
const
|
|
176
|
+
const body = await c.req.json();
|
|
177
|
+
if (workspaceUpdateRequestsAccountTransfer(body)) {
|
|
178
|
+
throw new ApiHttpError(409, {
|
|
179
|
+
code: "conflict",
|
|
180
|
+
message:
|
|
181
|
+
"A workspace is permanently owned by one organization; use workspace grants for same-organization access handoff.",
|
|
182
|
+
retryable: false,
|
|
183
|
+
outcomeUnknown: false,
|
|
184
|
+
details: { code: "workspace_transfer_unsupported" },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const payload = UpdateWorkspaceRequest.parse(body);
|
|
167
188
|
const workspace = await updateWorkspace(deps.db, workspaceId, {
|
|
168
189
|
...(payload.name !== undefined ? { name: payload.name.trim() } : {}),
|
|
169
190
|
...(payload.slug !== undefined ? { slug: payload.slug?.trim() || null } : {}),
|
|
@@ -363,6 +384,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
363
384
|
c.req.raw.signal,
|
|
364
385
|
{
|
|
365
386
|
observability: deps.observability,
|
|
387
|
+
actorEpoch: getManagedAuthRequestActorEpoch(c.req.raw) ?? undefined,
|
|
366
388
|
reauthorize: async () => {
|
|
367
389
|
await requireFreshAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
368
390
|
},
|