@opengeni/api-router 0.12.7 → 0.14.3

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 (89) hide show
  1. package/dist/app.d.ts +18 -38
  2. package/dist/app.js +3 -1
  3. package/dist/auth/managed-auth.d.ts +35 -0
  4. package/dist/{chunk-SD5GJTZ4.js → chunk-36PW33ND.js} +3744 -638
  5. package/dist/chunk-36PW33ND.js.map +1 -0
  6. package/dist/codex-redemption-security.d.ts +15 -0
  7. package/dist/github-access.d.ts +12 -0
  8. package/dist/github-browser-flow.d.ts +14 -0
  9. package/dist/http/auth.d.ts +3 -0
  10. package/dist/http/common.d.ts +3 -0
  11. package/dist/http/sse.d.ts +70 -0
  12. package/dist/index.d.ts +11 -13
  13. package/dist/index.js +1 -1
  14. package/dist/integrations/google-drive.d.ts +76 -0
  15. package/dist/integrations/oauth-client.d.ts +42 -0
  16. package/dist/integrations/provider-domain.d.ts +7 -0
  17. package/dist/integrations/slack-bot.d.ts +343 -0
  18. package/dist/mcp/documents.d.ts +8 -0
  19. package/dist/mcp/files.d.ts +5 -0
  20. package/dist/mcp/server.d.ts +107 -0
  21. package/dist/mcp/session-view.d.ts +191 -0
  22. package/dist/mcp/toolspace.d.ts +61 -0
  23. package/dist/model-catalog.d.ts +39 -0
  24. package/dist/observability.d.ts +3 -0
  25. package/dist/routes/api-keys.d.ts +3 -0
  26. package/dist/routes/billing.d.ts +16 -0
  27. package/dist/routes/capabilities.d.ts +3 -0
  28. package/dist/routes/catalog-assets.d.ts +5 -0
  29. package/dist/routes/codex.d.ts +10 -0
  30. package/dist/routes/connections.d.ts +3 -0
  31. package/dist/routes/documents.d.ts +3 -0
  32. package/dist/routes/enrollments.d.ts +3 -0
  33. package/dist/routes/environments.d.ts +5 -0
  34. package/dist/routes/files.d.ts +4 -0
  35. package/dist/routes/github.d.ts +3 -0
  36. package/dist/routes/insights.d.ts +3 -0
  37. package/dist/routes/install.d.ts +5 -0
  38. package/dist/routes/machines.d.ts +3 -0
  39. package/dist/routes/packs.d.ts +3 -0
  40. package/dist/routes/preference-registry.d.ts +5 -0
  41. package/dist/routes/rigs.d.ts +3 -0
  42. package/dist/routes/scheduled-tasks.d.ts +3 -0
  43. package/dist/routes/sessions.d.ts +15 -0
  44. package/dist/routes/social.d.ts +3 -0
  45. package/dist/routes/transcriptions.d.ts +3 -0
  46. package/dist/routes/workspace-capture.d.ts +59 -0
  47. package/dist/routes/workspace-instruction-policies.d.ts +3 -0
  48. package/dist/routes/workspace-state.d.ts +3 -0
  49. package/dist/routes/workspaces.d.ts +5 -0
  50. package/dist/sandbox/access.d.ts +21 -0
  51. package/dist/sandbox/auth-callout.d.ts +30 -0
  52. package/dist/sandbox/channel-a.d.ts +37 -0
  53. package/dist/sandbox/enrollment.d.ts +120 -0
  54. package/dist/sandbox/machines.d.ts +29 -0
  55. package/dist/sandbox/metrics-ingestion.d.ts +128 -0
  56. package/dist/sandbox/rematerialize.d.ts +24 -0
  57. package/dist/sandbox/viewer.d.ts +251 -0
  58. package/dist/transcription/providers/azure-openai.d.ts +9 -0
  59. package/dist/transcription/providers/codex-subscription.d.ts +9 -0
  60. package/dist/transcription/providers/openai.d.ts +9 -0
  61. package/dist/transcription/service.d.ts +10 -0
  62. package/dist/workspace-state-projection.d.ts +16 -0
  63. package/package.json +13 -13
  64. package/src/app.ts +75 -5
  65. package/src/github-access.ts +1 -0
  66. package/src/integrations/google-drive.ts +869 -0
  67. package/src/integrations/oauth-client.ts +41 -6
  68. package/src/integrations/slack-bot.ts +756 -26
  69. package/src/mcp/server.ts +226 -42
  70. package/src/mcp/session-view.ts +1 -0
  71. package/src/mcp/toolspace.ts +17 -9
  72. package/src/routes/connections.ts +137 -7
  73. package/src/routes/documents.ts +13 -2
  74. package/src/routes/github.ts +327 -20
  75. package/src/routes/insights.ts +30 -0
  76. package/src/routes/preference-registry.ts +482 -0
  77. package/src/routes/sessions.ts +22 -23
  78. package/src/routes/transcriptions.ts +155 -0
  79. package/src/routes/workspace-capture.ts +108 -8
  80. package/src/routes/workspace-state.ts +61 -0
  81. package/src/sandbox/channel-a.ts +2 -0
  82. package/src/sandbox/rematerialize.ts +111 -4
  83. package/src/sandbox/viewer.ts +29 -12
  84. package/src/transcription/providers/azure-openai.ts +45 -0
  85. package/src/transcription/providers/codex-subscription.ts +100 -0
  86. package/src/transcription/providers/openai.ts +93 -0
  87. package/src/transcription/service.ts +121 -0
  88. package/src/workspace-state-projection.ts +244 -0
  89. package/dist/chunk-SD5GJTZ4.js.map +0 -1
@@ -0,0 +1,482 @@
1
+ import {
2
+ ActivatePreferenceRegistryRevisionRequest,
3
+ ChangePreferenceRegistryScopeRequest,
4
+ CorrectPreferenceRegistryRequest,
5
+ CreatePreferenceRegistryProposalRequest,
6
+ DeactivatePreferenceRegistryRequest,
7
+ PreferenceRegistryConflictResponse,
8
+ PreferenceRegistryDetailResponse,
9
+ PreferenceRegistryFullContentRequest,
10
+ PreferenceRegistryFullContent,
11
+ PreferenceRegistryListQuery,
12
+ PreferenceRegistryListResponse,
13
+ PreferenceRegistryMutationResponse,
14
+ PreferenceRegistryRecord,
15
+ PreferenceRegistrySnapshot,
16
+ RejectPreferenceRegistryProposalRequest,
17
+ SupersedePreferenceRegistryRequest,
18
+ type AccessGrant,
19
+ type PreferenceRegistryScope,
20
+ } from "@opengeni/contracts";
21
+ import {
22
+ hasPermission,
23
+ requireAccessGrant,
24
+ requireAccessGrantAuthorization,
25
+ type AccessGrantAuthorization,
26
+ type ApiRouteDeps,
27
+ } from "@opengeni/core";
28
+ import {
29
+ activatePreferenceRegistryRevision,
30
+ changePreferenceRegistryScope,
31
+ correctPreferenceRegistry,
32
+ createPreferenceRegistryProposal,
33
+ deactivatePreferenceRegistry,
34
+ getOrCreatePreferenceRegistrySnapshot,
35
+ getPreferenceRegistryDetail,
36
+ getPreferenceRegistryDetailForAttempt,
37
+ getPreferenceRegistryFullContent,
38
+ listPreferenceRegistry,
39
+ listPreferenceRegistryForAttempt,
40
+ PreferenceRegistryConflictError,
41
+ PreferenceRegistryInitiatorError,
42
+ PreferenceRegistryInvalidOperationError,
43
+ PreferenceRegistryNotFoundError,
44
+ PreferenceRegistryStableKeyConflictError,
45
+ rejectPreferenceRegistryProposal,
46
+ supersedePreferenceRegistry,
47
+ type PreferenceRegistryAttemptClaims,
48
+ } from "@opengeni/db";
49
+ import type { Context, Hono } from "hono";
50
+ import { HTTPException } from "hono/http-exception";
51
+ import { z } from "zod";
52
+
53
+ const Id = z.string().uuid();
54
+
55
+ async function parseBody<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
56
+ const parsed = schema.safeParse(await context.req.json().catch(() => null));
57
+ if (!parsed.success)
58
+ throw new HTTPException(422, { message: "Invalid preference registry request" });
59
+ return parsed.data;
60
+ }
61
+
62
+ function exactAttemptClaims(grant: AccessGrant) {
63
+ const metadata = grant.metadata ?? {};
64
+ const values = {
65
+ sessionId: metadata["sessionId"],
66
+ turnId: metadata["turnId"],
67
+ attemptId: metadata["attemptId"],
68
+ executionGeneration: metadata["executionGeneration"],
69
+ };
70
+ const present = Object.values(values).filter((value) => value !== undefined).length;
71
+ if (present === 0) return null;
72
+ if (
73
+ typeof values.sessionId !== "string" ||
74
+ typeof values.turnId !== "string" ||
75
+ typeof values.attemptId !== "string" ||
76
+ typeof values.executionGeneration !== "number" ||
77
+ !Number.isSafeInteger(values.executionGeneration) ||
78
+ values.executionGeneration < 1 ||
79
+ values.executionGeneration > 2_147_483_647
80
+ ) {
81
+ throw new HTTPException(403, { message: "Incomplete signed preference attempt authority" });
82
+ }
83
+ return values as {
84
+ sessionId: string;
85
+ turnId: string;
86
+ attemptId: string;
87
+ executionGeneration: number;
88
+ };
89
+ }
90
+
91
+ function requiredAttemptClaims(grant: AccessGrant): PreferenceRegistryAttemptClaims {
92
+ const claims = exactAttemptClaims(grant);
93
+ if (!claims) {
94
+ throw new HTTPException(403, {
95
+ message: "Preference snapshot retrieval requires a signed session, turn, and attempt",
96
+ });
97
+ }
98
+ return {
99
+ accountId: grant.accountId,
100
+ workspaceId: grant.workspaceId,
101
+ ...claims,
102
+ };
103
+ }
104
+
105
+ function requireHumanMutation(access: AccessGrantAuthorization): void {
106
+ const { grant } = access;
107
+ if (
108
+ !access.contextIntegrity ||
109
+ access.authenticatedSubjectId !== grant.subjectId ||
110
+ grant.principalKind !== "human_session" ||
111
+ exactAttemptClaims(grant) !== null ||
112
+ grant.serviceInitiator ||
113
+ grant.serviceInitiatorContext ||
114
+ grant.subjectId.startsWith("api_key:")
115
+ ) {
116
+ throw new HTTPException(403, {
117
+ message: "Preference activation and scope mutation require a direct human-authorized request",
118
+ });
119
+ }
120
+ }
121
+
122
+ export function authorizePreferenceRegistryScopeMutation(
123
+ access: AccessGrantAuthorization,
124
+ scope: PreferenceRegistryScope,
125
+ ): void {
126
+ const { grant } = access;
127
+ requireHumanMutation(access);
128
+ if (scope === "organization") {
129
+ // Deliberately do not use hasPermission: workspace:admin must not expand to
130
+ // account:admin for an organization-wide preference. Account authority is
131
+ // carried separately from the authenticated context and must match this
132
+ // exact workspace grant's account and subject.
133
+ if (!access.accountGrant?.permissions.includes("account:admin")) {
134
+ throw new HTTPException(403, { message: "missing permission: account:admin" });
135
+ }
136
+ return;
137
+ }
138
+ if (scope === "workspace" && !hasPermission(grant.permissions, "workspace:admin")) {
139
+ throw new HTTPException(403, { message: "missing permission: workspace:admin" });
140
+ }
141
+ // User scope is always self-only: the route derives the target from the
142
+ // authenticated actor and never accepts a subject selector.
143
+ }
144
+
145
+ function preferenceError(context: Context, error: unknown): Response {
146
+ if (error instanceof PreferenceRegistryConflictError) {
147
+ return context.json(
148
+ PreferenceRegistryConflictResponse.parse({
149
+ code: error.code,
150
+ message: error.message,
151
+ currentRevisionId: error.currentRevisionId,
152
+ scopeVersion: error.scopeVersion,
153
+ }),
154
+ 409,
155
+ );
156
+ }
157
+ if (error instanceof PreferenceRegistryNotFoundError) {
158
+ return context.json({ code: "PREFERENCE_REGISTRY_NOT_FOUND", message: error.message }, 404);
159
+ }
160
+ if (error instanceof PreferenceRegistryStableKeyConflictError) {
161
+ return context.json({ code: error.code, message: error.message }, 409);
162
+ }
163
+ if (error instanceof PreferenceRegistryInvalidOperationError) {
164
+ return context.json(
165
+ { code: "INVALID_PREFERENCE_REGISTRY_OPERATION", message: error.message },
166
+ 422,
167
+ );
168
+ }
169
+ if (error instanceof PreferenceRegistryInitiatorError) {
170
+ return context.json(
171
+ { code: "PREFERENCE_REGISTRY_ATTEMPT_REJECTED", message: error.message },
172
+ 403,
173
+ );
174
+ }
175
+ throw error;
176
+ }
177
+
178
+ function preferenceId(context: Context): string {
179
+ const parsed = Id.safeParse(context.req.param("preferenceId"));
180
+ if (!parsed.success) throw new HTTPException(422, { message: "Invalid preference id" });
181
+ return parsed.data;
182
+ }
183
+
184
+ export function registerPreferenceRegistryRoutes(app: Hono, deps: ApiRouteDeps): void {
185
+ const base = "/v1/workspaces/:workspaceId/preferences";
186
+
187
+ app.get(base, async (context) => {
188
+ const workspaceId = context.req.param("workspaceId");
189
+ const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
190
+ const query = PreferenceRegistryListQuery.safeParse({
191
+ scope: context.req.query("scope"),
192
+ status: context.req.query("status"),
193
+ limit: context.req.query("limit"),
194
+ });
195
+ if (!query.success)
196
+ throw new HTTPException(422, { message: "Invalid preference registry query" });
197
+ try {
198
+ const claims = exactAttemptClaims(grant);
199
+ const result = claims
200
+ ? await listPreferenceRegistryForAttempt(deps.db, {
201
+ accountId: grant.accountId,
202
+ workspaceId,
203
+ ...claims,
204
+ ...query.data,
205
+ })
206
+ : await listPreferenceRegistry(deps.db, {
207
+ workspaceId,
208
+ subjectId: grant.subjectId,
209
+ ...query.data,
210
+ });
211
+ return context.json(PreferenceRegistryListResponse.parse(result));
212
+ } catch (error) {
213
+ return preferenceError(context, error);
214
+ }
215
+ });
216
+
217
+ app.post(`${base}/proposals`, async (context) => {
218
+ const workspaceId = context.req.param("workspaceId");
219
+ const access = await requireAccessGrantAuthorization(
220
+ context,
221
+ deps,
222
+ workspaceId,
223
+ "workspace:read",
224
+ );
225
+ const { grant } = access;
226
+ const request = await parseBody(context, CreatePreferenceRegistryProposalRequest);
227
+ authorizePreferenceRegistryScopeMutation(access, request.scope);
228
+ try {
229
+ return context.json(
230
+ PreferenceRegistryRecord.parse(
231
+ await createPreferenceRegistryProposal(deps.db, {
232
+ ...request,
233
+ accountId: grant.accountId,
234
+ workspaceId,
235
+ actorSubjectId: grant.subjectId,
236
+ principalKind: grant.principalKind,
237
+ }),
238
+ ),
239
+ 201,
240
+ );
241
+ } catch (error) {
242
+ return preferenceError(context, error);
243
+ }
244
+ });
245
+
246
+ app.get(`${base}/summary`, async (context) => {
247
+ const workspaceId = context.req.param("workspaceId");
248
+ const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
249
+ try {
250
+ return context.json(
251
+ PreferenceRegistrySnapshot.parse(
252
+ await getOrCreatePreferenceRegistrySnapshot(deps.db, requiredAttemptClaims(grant)),
253
+ ),
254
+ );
255
+ } catch (error) {
256
+ return preferenceError(context, error);
257
+ }
258
+ });
259
+
260
+ app.post(`${base}/full-content`, async (context) => {
261
+ const workspaceId = context.req.param("workspaceId");
262
+ const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
263
+ const request = await parseBody(context, PreferenceRegistryFullContentRequest);
264
+ try {
265
+ return context.json(
266
+ PreferenceRegistryFullContent.parse(
267
+ await getPreferenceRegistryFullContent(
268
+ deps.db,
269
+ requiredAttemptClaims(grant),
270
+ request.retrievalHandle,
271
+ ),
272
+ ),
273
+ );
274
+ } catch (error) {
275
+ return preferenceError(context, error);
276
+ }
277
+ });
278
+
279
+ app.get(`${base}/:preferenceId`, async (context) => {
280
+ const workspaceId = context.req.param("workspaceId");
281
+ const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:read");
282
+ try {
283
+ const claims = exactAttemptClaims(grant);
284
+ const id = preferenceId(context);
285
+ const detail = claims
286
+ ? await getPreferenceRegistryDetailForAttempt(deps.db, {
287
+ accountId: grant.accountId,
288
+ workspaceId,
289
+ ...claims,
290
+ preferenceId: id,
291
+ })
292
+ : await getPreferenceRegistryDetail(deps.db, {
293
+ workspaceId,
294
+ subjectId: grant.subjectId,
295
+ preferenceId: id,
296
+ });
297
+ return context.json(PreferenceRegistryDetailResponse.parse(detail));
298
+ } catch (error) {
299
+ return preferenceError(context, error);
300
+ }
301
+ });
302
+
303
+ app.post(`${base}/:preferenceId/activate`, async (context) => {
304
+ const workspaceId = context.req.param("workspaceId");
305
+ const access = await requireAccessGrantAuthorization(
306
+ context,
307
+ deps,
308
+ workspaceId,
309
+ "workspace:read",
310
+ );
311
+ const { grant } = access;
312
+ const id = preferenceId(context);
313
+ const request = await parseBody(context, ActivatePreferenceRegistryRevisionRequest);
314
+ try {
315
+ return context.json(
316
+ PreferenceRegistryMutationResponse.parse(
317
+ await activatePreferenceRegistryRevision(deps.db, {
318
+ accountId: grant.accountId,
319
+ workspaceId,
320
+ actorSubjectId: grant.subjectId,
321
+ principalKind: grant.principalKind,
322
+ preferenceId: id,
323
+ authorizeScope: (scope) => authorizePreferenceRegistryScopeMutation(access, scope),
324
+ ...request,
325
+ }),
326
+ ),
327
+ );
328
+ } catch (error) {
329
+ return preferenceError(context, error);
330
+ }
331
+ });
332
+
333
+ app.post(`${base}/:preferenceId/correct`, async (context) => {
334
+ const workspaceId = context.req.param("workspaceId");
335
+ const access = await requireAccessGrantAuthorization(
336
+ context,
337
+ deps,
338
+ workspaceId,
339
+ "workspace:read",
340
+ );
341
+ const { grant } = access;
342
+ const id = preferenceId(context);
343
+ const request = await parseBody(context, CorrectPreferenceRegistryRequest);
344
+ try {
345
+ return context.json(
346
+ PreferenceRegistryMutationResponse.parse(
347
+ await correctPreferenceRegistry(deps.db, {
348
+ ...request,
349
+ accountId: grant.accountId,
350
+ workspaceId,
351
+ actorSubjectId: grant.subjectId,
352
+ principalKind: grant.principalKind,
353
+ preferenceId: id,
354
+ authorizeScope: (scope) => authorizePreferenceRegistryScopeMutation(access, scope),
355
+ }),
356
+ ),
357
+ );
358
+ } catch (error) {
359
+ return preferenceError(context, error);
360
+ }
361
+ });
362
+
363
+ app.post(`${base}/:preferenceId/scope`, async (context) => {
364
+ const workspaceId = context.req.param("workspaceId");
365
+ const access = await requireAccessGrantAuthorization(
366
+ context,
367
+ deps,
368
+ workspaceId,
369
+ "workspace:read",
370
+ );
371
+ const { grant } = access;
372
+ const id = preferenceId(context);
373
+ const request = await parseBody(context, ChangePreferenceRegistryScopeRequest);
374
+ try {
375
+ return context.json(
376
+ PreferenceRegistryMutationResponse.parse(
377
+ await changePreferenceRegistryScope(deps.db, {
378
+ ...request,
379
+ accountId: grant.accountId,
380
+ workspaceId,
381
+ actorSubjectId: grant.subjectId,
382
+ principalKind: grant.principalKind,
383
+ preferenceId: id,
384
+ authorizeScope: (scope) => authorizePreferenceRegistryScopeMutation(access, scope),
385
+ }),
386
+ ),
387
+ );
388
+ } catch (error) {
389
+ return preferenceError(context, error);
390
+ }
391
+ });
392
+
393
+ app.post(`${base}/:preferenceId/deactivate`, async (context) => {
394
+ const workspaceId = context.req.param("workspaceId");
395
+ const access = await requireAccessGrantAuthorization(
396
+ context,
397
+ deps,
398
+ workspaceId,
399
+ "workspace:read",
400
+ );
401
+ const { grant } = access;
402
+ const id = preferenceId(context);
403
+ const request = await parseBody(context, DeactivatePreferenceRegistryRequest);
404
+ try {
405
+ return context.json(
406
+ PreferenceRegistryMutationResponse.parse(
407
+ await deactivatePreferenceRegistry(deps.db, {
408
+ ...request,
409
+ accountId: grant.accountId,
410
+ workspaceId,
411
+ actorSubjectId: grant.subjectId,
412
+ principalKind: grant.principalKind,
413
+ preferenceId: id,
414
+ authorizeScope: (scope) => authorizePreferenceRegistryScopeMutation(access, scope),
415
+ }),
416
+ ),
417
+ );
418
+ } catch (error) {
419
+ return preferenceError(context, error);
420
+ }
421
+ });
422
+
423
+ app.post(`${base}/:preferenceId/supersede`, async (context) => {
424
+ const workspaceId = context.req.param("workspaceId");
425
+ const access = await requireAccessGrantAuthorization(
426
+ context,
427
+ deps,
428
+ workspaceId,
429
+ "workspace:read",
430
+ );
431
+ const { grant } = access;
432
+ const id = preferenceId(context);
433
+ const request = await parseBody(context, SupersedePreferenceRegistryRequest);
434
+ try {
435
+ return context.json(
436
+ PreferenceRegistryMutationResponse.parse(
437
+ await supersedePreferenceRegistry(deps.db, {
438
+ ...request,
439
+ accountId: grant.accountId,
440
+ workspaceId,
441
+ actorSubjectId: grant.subjectId,
442
+ principalKind: grant.principalKind,
443
+ preferenceId: id,
444
+ authorizeScope: (scope) => authorizePreferenceRegistryScopeMutation(access, scope),
445
+ }),
446
+ ),
447
+ );
448
+ } catch (error) {
449
+ return preferenceError(context, error);
450
+ }
451
+ });
452
+
453
+ app.post(`${base}/:preferenceId/reject`, async (context) => {
454
+ const workspaceId = context.req.param("workspaceId");
455
+ const access = await requireAccessGrantAuthorization(
456
+ context,
457
+ deps,
458
+ workspaceId,
459
+ "workspace:read",
460
+ );
461
+ const { grant } = access;
462
+ const id = preferenceId(context);
463
+ const request = await parseBody(context, RejectPreferenceRegistryProposalRequest);
464
+ try {
465
+ return context.json(
466
+ PreferenceRegistryMutationResponse.parse(
467
+ await rejectPreferenceRegistryProposal(deps.db, {
468
+ ...request,
469
+ accountId: grant.accountId,
470
+ workspaceId,
471
+ actorSubjectId: grant.subjectId,
472
+ principalKind: grant.principalKind,
473
+ preferenceId: id,
474
+ authorizeScope: (scope) => authorizePreferenceRegistryScopeMutation(access, scope),
475
+ }),
476
+ ),
477
+ );
478
+ } catch (error) {
479
+ return preferenceError(context, error);
480
+ }
481
+ });
482
+ }
@@ -76,6 +76,7 @@ import {
76
76
  listSessionHumanInputRequests,
77
77
  listSessionIdsInGroup,
78
78
  listSessionsForSubject,
79
+ getLatestStartedSessionTurn,
79
80
  listSessionTurns,
80
81
  projectEffectiveControlForRelatedAccess,
81
82
  projectSessionForRelatedAccess,
@@ -102,6 +103,7 @@ import {
102
103
  SessionContextBusyError,
103
104
  HumanInputResponseValidationError,
104
105
  latestWorkspaceCapture,
106
+ sessionLatestWorkspaceCapture,
105
107
  workspaceCaptureAtRevision,
106
108
  type AppendEventInput,
107
109
  type SandboxOpenPtySessionRow,
@@ -167,12 +169,17 @@ import {
167
169
  } from "@opengeni/core";
168
170
  import { assertSessionExists, boundedLimit } from "../http/common";
169
171
  import { sseSessionStream } from "../http/sse";
170
- import { serveWorkspaceCapture, serveWorkspaceCaptureFile } from "./workspace-capture";
172
+ import {
173
+ serveWorkspaceCapture,
174
+ serveWorkspaceCaptureFile,
175
+ WorkspaceCaptureManifestCache,
176
+ } from "./workspace-capture";
171
177
 
172
178
  type SessionRouteDeps = ApiRouteDeps & Pick<ViewerServices, "establishSandboxSession">;
173
179
 
174
180
  export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
175
181
  const { settings, db, bus, workflowClient, objectStorage } = deps;
182
+ const workspaceCaptureManifestCache = new WorkspaceCaptureManifestCache();
176
183
  const ptyIdentity = (pty: SandboxOpenPtySessionRow): SandboxPtyProcessIdentity => ({
177
184
  leaseId: pty.leaseId,
178
185
  sandboxGroupId: pty.sandboxGroupId,
@@ -1071,6 +1078,13 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
1071
1078
  await requireAccessGrant(c, deps, workspaceId, "sessions:read");
1072
1079
  const sessionId = c.req.param("sessionId");
1073
1080
  await assertSessionExists(db, workspaceId, sessionId);
1081
+ // Exact turn that most recently emitted durable `turn.started` — the same
1082
+ // boundary goal continuations use for inherited model/effort. Queued-only
1083
+ // or preflight-rejected turns are deliberately excluded.
1084
+ if (c.req.query("latestStarted") === "1" || c.req.query("latestStarted") === "true") {
1085
+ const latest = await getLatestStartedSessionTurn(db, workspaceId, sessionId);
1086
+ return c.json(latest ? [latest] : []);
1087
+ }
1074
1088
  return c.json(
1075
1089
  await listSessionTurns(db, workspaceId, sessionId, boundedLimit(c.req.query("limit"))),
1076
1090
  );
@@ -1276,10 +1290,9 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
1276
1290
  text: payload.text,
1277
1291
  turnInstructions: payload.turnInstructions ?? null,
1278
1292
  resources: payload.resources,
1279
- tools: payload.tools,
1280
- toolsProvided: userMessagePayloadHasOwnProperty({ payload: raw }, "tools"),
1281
1293
  model: payload.model ?? null,
1282
1294
  reasoningEffort: payload.reasoningEffort ?? null,
1295
+ latencyMode: payload.latencyMode ?? null,
1283
1296
  mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
1284
1297
  delivery: "steer",
1285
1298
  origin: "human",
@@ -1320,10 +1333,9 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
1320
1333
  text: event.payload.text,
1321
1334
  turnInstructions: event.payload.turnInstructions ?? null,
1322
1335
  resources: event.payload.resources ?? [],
1323
- tools: event.payload.tools ?? [],
1324
- toolsProvided: userMessagePayloadHasOwnProperty(rawEvent, "tools"),
1325
1336
  model: event.payload.model ?? null,
1326
1337
  reasoningEffort: event.payload.reasoningEffort ?? null,
1338
+ latencyMode: event.payload.latencyMode ?? null,
1327
1339
  mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
1328
1340
  ...(event.payload.controlEtag !== undefined
1329
1341
  ? { controlEtag: event.payload.controlEtag }
@@ -2120,16 +2132,17 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2120
2132
  const workspaceId = c.req.param("workspaceId") ?? "";
2121
2133
  await requireAccessGrant(c, deps, workspaceId, "files:read");
2122
2134
  const sessionId = c.req.param("sessionId") ?? "";
2123
- const session = await getSession(db, workspaceId, sessionId);
2124
- if (!session) {
2135
+ const lookup = await sessionLatestWorkspaceCapture(db, workspaceId, sessionId);
2136
+ if (!lookup.sessionExists) {
2125
2137
  throw new HTTPException(404, { message: "session not found" });
2126
2138
  }
2127
2139
  if (!objectStorage) {
2128
2140
  // No storage configured → no captures can exist. Cold-fallback, not an error.
2129
2141
  return c.json({ available: false });
2130
2142
  }
2131
- const row = await latestWorkspaceCapture(db, workspaceId, sessionId);
2132
- return c.json(await serveWorkspaceCapture(row, objectStorage));
2143
+ return c.json(
2144
+ await serveWorkspaceCapture(lookup.capture, objectStorage, workspaceCaptureManifestCache),
2145
+ );
2133
2146
  });
2134
2147
 
2135
2148
  app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/workspace/capture/file", async (c) => {
@@ -2650,20 +2663,6 @@ function optionalEventSequence(raw: string | undefined): number | undefined {
2650
2663
  return Math.floor(sequence);
2651
2664
  }
2652
2665
 
2653
- function hasOwnProperty(value: unknown, key: string): boolean {
2654
- return Boolean(
2655
- value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key),
2656
- );
2657
- }
2658
-
2659
- function userMessagePayloadHasOwnProperty(value: unknown, key: string): boolean {
2660
- if (!value || typeof value !== "object") {
2661
- return false;
2662
- }
2663
- const payload = (value as { payload?: unknown }).payload;
2664
- return hasOwnProperty(payload, key);
2665
- }
2666
-
2667
2666
  /** Stable, value-free JSON errors for only the create-session boundary. */
2668
2667
  export function sessionCreateErrorResponse(c: Context, error: unknown): Response {
2669
2668
  if (error instanceof SessionSpawnDeniedError) {