@opengeni/api-router 3.0.1-canary.0 → 3.0.1-canary.2

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.
@@ -52,8 +52,8 @@ export declare function marketplacePlugin(url: string): {
52
52
  category: string;
53
53
  keywords: never[];
54
54
  author: {
55
- name: string;
56
55
  url?: never;
56
+ name: string;
57
57
  email?: never;
58
58
  };
59
59
  version: string;
@@ -78,9 +78,9 @@ export declare function marketplacePlugin(url: string): {
78
78
  installation: string;
79
79
  upstream: {
80
80
  source: {
81
+ url?: never;
81
82
  source: string;
82
83
  path: string;
83
- url?: never;
84
84
  };
85
85
  policy: {
86
86
  installation: string;
@@ -124,9 +124,9 @@ export declare function marketplacePlugin(url: string): {
124
124
  installation: string;
125
125
  upstream: {
126
126
  source: {
127
+ url?: never;
127
128
  source: string;
128
129
  path: string;
129
- url?: never;
130
130
  };
131
131
  policy: {
132
132
  installation: string;
@@ -170,9 +170,9 @@ export declare function marketplacePlugin(url: string): {
170
170
  installation: string;
171
171
  upstream: {
172
172
  source: {
173
+ url?: never;
173
174
  source: string;
174
175
  path: string;
175
- url?: never;
176
176
  };
177
177
  policy: {
178
178
  installation: string;
@@ -216,9 +216,9 @@ export declare function marketplacePlugin(url: string): {
216
216
  installation: string;
217
217
  upstream: {
218
218
  source: {
219
+ url?: never;
219
220
  source: string;
220
221
  path: string;
221
- url?: never;
222
222
  };
223
223
  policy: {
224
224
  installation: string;
@@ -262,9 +262,9 @@ export declare function marketplacePlugin(url: string): {
262
262
  installation: string;
263
263
  upstream: {
264
264
  source: {
265
+ url?: never;
265
266
  source: string;
266
267
  path: string;
267
- url?: never;
268
268
  };
269
269
  policy: {
270
270
  installation: string;
@@ -308,9 +308,9 @@ export declare function marketplacePlugin(url: string): {
308
308
  installation: string;
309
309
  upstream: {
310
310
  source: {
311
+ url?: never;
311
312
  source: string;
312
313
  path: string;
313
- url?: never;
314
314
  };
315
315
  policy: {
316
316
  installation: string;
@@ -82,8 +82,8 @@ export declare function scheduledTaskMcpSummary(task: ScheduledTask): {
82
82
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
83
83
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
84
84
  action: {
85
- readonly sourceId?: never;
86
85
  kind: "agent_turn";
86
+ sourceId?: never;
87
87
  sourceGeneration?: never;
88
88
  sourceLifecycleGeneration?: never;
89
89
  destination?: never;
@@ -205,8 +205,8 @@ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
205
205
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
206
206
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
207
207
  action: {
208
- readonly sourceId?: never;
209
208
  kind: "agent_turn";
209
+ sourceId?: never;
210
210
  sourceGeneration?: never;
211
211
  sourceLifecycleGeneration?: never;
212
212
  destination?: never;
@@ -389,8 +389,8 @@ export declare function boundScheduledTaskMcpPage(input: {
389
389
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
390
390
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
391
391
  action: {
392
- readonly sourceId?: never;
393
392
  kind: "agent_turn";
393
+ sourceId?: never;
394
394
  sourceGeneration?: never;
395
395
  sourceLifecycleGeneration?: never;
396
396
  destination?: never;
@@ -1,5 +1,5 @@
1
- import { ClientSessionEvent, SteerSessionMessageRequest, type SessionStatus, type WorkClaimSubjectFilter, type SessionGoalRevision, type SessionAuthorizationOperation } from "@opengeni/contracts";
2
- import { type SessionDiscoveryCursor } from "@opengeni/db";
1
+ import { ClientSessionEvent, SessionScopeSubjectId, SteerSessionMessageRequest, type SessionStatus, type WorkClaimSubjectFilter, type SessionGoalRevision, type SessionAuthorizationOperation } from "@opengeni/contracts";
2
+ import { decodeSessionListCursor, type SessionDiscoveryCursor } from "@opengeni/db";
3
3
  import type { Context, Hono } from "hono";
4
4
  import type { ApiRouteDeps } from "@opengeni/core";
5
5
  import { type ViewerServices } from "../sandbox/viewer.js";
@@ -13,6 +13,28 @@ export declare function goalProposalMatchesExpectedRevision(revision: Pick<Sessi
13
13
  */
14
14
  export declare function sessionAuthorizationOperationForHttp(method: string, pathname: string, sessionId: string): SessionAuthorizationOperation | null;
15
15
  export declare function sessionTenancyHttpError(error: unknown): Error;
16
+ export declare function sessionListQuery(query: Record<string, string>, allowCursor?: boolean): {
17
+ originSiteId: string | undefined;
18
+ limit: string | undefined;
19
+ parentSessionId: string | null | undefined;
20
+ cursor: ReturnType<typeof decodeSessionListCursor> | undefined;
21
+ search: string | undefined;
22
+ pinsOnly: boolean;
23
+ archivedOnly: boolean;
24
+ sortBy: "updatedAt" | "createdAt" | "name" | undefined;
25
+ archiveStatus: "active" | "archived" | "all" | undefined;
26
+ channelId: string | null | undefined;
27
+ createdBy: {
28
+ kind: "subject" | "service";
29
+ subjectId: string;
30
+ } | undefined;
31
+ updatedFrom: Date | undefined;
32
+ updatedBefore: Date | undefined;
33
+ createdFrom: Date | undefined;
34
+ createdBefore: Date | undefined;
35
+ scopeSubjectId: SessionScopeSubjectId | undefined;
36
+ hasPageFilters: boolean;
37
+ };
16
38
  export type AgentTopologyCursorEnvelope = {
17
39
  cursor: SessionDiscoveryCursor;
18
40
  parentSessionId: string | null | "all";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "3.0.1-canary.0",
3
+ "version": "3.0.1-canary.2",
4
4
  "description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -43,24 +43,24 @@
43
43
  "@hono/zod-validator": "^0.7.6",
44
44
  "@llamaindex/liteparse": "2.14.2",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
- "@opengeni/agent-proto": "^0.6.0-canary.5",
47
- "@opengeni/artifact-tool": "^0.3.29-canary.0",
48
- "@opengeni/capabilities": "^0.3.4-canary.0",
49
- "@opengeni/codemode": "^0.5.9-canary.0",
50
- "@opengeni/codex": "^0.2.23-canary.2",
51
- "@opengeni/config": "^1.2.2-canary.0",
52
- "@opengeni/contracts": "^4.1.0-canary.0",
53
- "@opengeni/core": "^3.0.1-canary.0",
54
- "@opengeni/db": "^5.0.1-canary.0",
55
- "@opengeni/documents": "^0.8.30-canary.0",
56
- "@opengeni/events": "^0.4.28-canary.0",
57
- "@opengeni/github": "^0.7.13-canary.0",
58
- "@opengeni/network": "^0.3.1-canary.17",
59
- "@opengeni/observability": "^0.8.29-canary.0",
60
- "@opengeni/runtime": "^3.0.1-canary.0",
61
- "@opengeni/storage": "^0.2.130-canary.0",
62
- "@opengeni/tool-gateway": "^0.1.10-canary.0",
63
- "@opengeni/xai-subscription": "^0.1.4-canary.17",
46
+ "@opengeni/agent-proto": "^0.6.0-canary.7",
47
+ "@opengeni/artifact-tool": "^0.3.29-canary.2",
48
+ "@opengeni/capabilities": "^0.3.4-canary.2",
49
+ "@opengeni/codemode": "^0.5.9-canary.2",
50
+ "@opengeni/codex": "^0.2.23-canary.4",
51
+ "@opengeni/config": "^1.2.2-canary.2",
52
+ "@opengeni/contracts": "^4.1.0-canary.2",
53
+ "@opengeni/core": "^3.0.1-canary.2",
54
+ "@opengeni/db": "^5.0.1-canary.2",
55
+ "@opengeni/documents": "^0.8.30-canary.2",
56
+ "@opengeni/events": "^0.4.28-canary.2",
57
+ "@opengeni/github": "^0.7.13-canary.2",
58
+ "@opengeni/network": "^0.3.1-canary.19",
59
+ "@opengeni/observability": "^0.8.29-canary.2",
60
+ "@opengeni/runtime": "^3.0.1-canary.2",
61
+ "@opengeni/storage": "^0.2.130-canary.2",
62
+ "@opengeni/tool-gateway": "^0.1.10-canary.2",
63
+ "@opengeni/xai-subscription": "^0.1.4-canary.19",
64
64
  "@temporalio/client": "^1.17.0",
65
65
  "better-auth": "^1.6.14",
66
66
  "hono": "^4.12.18",
@@ -47,6 +47,7 @@ import {
47
47
  PostgresEditableArtifactStore,
48
48
  listEditableArtifactIdsForSession,
49
49
  touchEditableArtifactSessionLink,
50
+ recordNativeDocumentInspection,
50
51
  dbSql,
51
52
  withRlsContext,
52
53
  type Database,
@@ -167,6 +168,11 @@ export async function createStandaloneEditableArtifactApplication(input: {
167
168
  await touchEditableArtifactSessionLink(input.db, scope, sessionId, artifactId),
168
169
  } satisfies EditableArtifactAgentAssociationPort),
169
170
  inspector: kernel,
171
+ recordInspection: async (inspection) => {
172
+ if (inspection.actor.kind !== "agent")
173
+ throw new Error("Inspection proof requires an agent attempt");
174
+ return recordNativeDocumentInspection(input.db, { ...inspection, actor: inspection.actor });
175
+ },
170
176
  officeImports,
171
177
  workspaceFiles: new EditableArtifactWorkspaceFileAdapter({
172
178
  db: input.db,
@@ -38,6 +38,7 @@ const ArtifactCommand = z
38
38
  const ArtifactMetadata = z
39
39
  .object({
40
40
  id: ArtifactId,
41
+ artifactReference: z.string(),
41
42
  modality: Modality,
42
43
  title: z.string(),
43
44
  lifecycle: z.enum(["active", "archived"]),
@@ -203,13 +204,17 @@ export function registerEditableArtifactAgentTools(
203
204
  {
204
205
  title: "Inspect editable artifact",
205
206
  description:
206
- "Query the artifact's current canonical head through the same native kernel used by the browser. Use bounded modality queries; results are current, not a sandbox-file snapshot.",
207
+ "Query the artifact's current canonical head through the same native kernel used by the browser. Use bounded modality queries; results are current, not a sandbox-file snapshot. Native document queries return a server-issued inspectionReceiptId; use a post-edit body inspection receipt for declared report completion. If the document changes during inspection, retry against its current head.",
207
208
  inputSchema: {
208
209
  artifactId: ArtifactId,
209
210
  modality: Modality,
210
211
  request: EditableArtifactQueryRequestSchema,
211
212
  },
212
- outputSchema: { artifact: ArtifactMetadata, projection: z.unknown() },
213
+ outputSchema: {
214
+ artifact: ArtifactMetadata,
215
+ projection: z.unknown(),
216
+ inspectionReceiptId: z.string().uuid().optional(),
217
+ },
213
218
  annotations: readOnlyAnnotations("Inspect editable artifact"),
214
219
  },
215
220
  async ({ artifactId, modality, request }, extra) =>
package/src/mcp/server.ts CHANGED
@@ -18,6 +18,8 @@ import {
18
18
  CreateScheduledTaskRequest,
19
19
  CreateSessionRequest,
20
20
  GoalSpec,
21
+ SessionGoalReportRequirements,
22
+ SessionGoalReportDeliveries,
21
23
  boundSessionMcpText as capSessionDiscoveryText,
22
24
  compactSessionMcpListRow,
23
25
  sessionMcpIncludesRelatedWork,
@@ -160,6 +162,7 @@ import type { AnySchema, ZodRawShapeCompat } from "@modelcontextprotocol/sdk/ser
160
162
  import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
161
163
  import { HTTPException } from "hono/http-exception";
162
164
  import * as z4 from "zod/v4";
165
+ import { editableArtifactActorForGrant } from "../routes/editable-artifacts";
163
166
  import { registerKnowledgeEntryTools } from "./knowledge-entries";
164
167
  import {
165
168
  FIRST_PARTY_TOOL_AUTHORIZATION,
@@ -2420,7 +2423,7 @@ function registerGoalTools(
2420
2423
  "goal_set",
2421
2424
  {
2422
2425
  description:
2423
- "Create a goal when this session has none, or replace a completed goal with a new one. While active, idle moments synthesize continuation turns until goal_complete or goal_pause. To change an active or paused goal, use goal_update with its objective revision, a change kind, and rationale.",
2426
+ "Create a goal when this session has none, or replace a completed goal with a new one. Declare user-facing native document reports with reportRequirements before producing them. While active, idle moments synthesize continuation turns until goal_complete or goal_pause. To change an active or paused goal, use goal_update with its objective revision, a change kind, and rationale.",
2424
2427
  // `maxAutoContinuations` is deliberately not agent-facing: the ceiling is
2425
2428
  // API/scheduled-task pacing configuration, and an agent that set its own
2426
2429
  // cap used to silence its orchestration for hours. Continuation pacing is
@@ -2428,9 +2431,10 @@ function registerGoalTools(
2428
2431
  inputSchema: {
2429
2432
  text: goalText,
2430
2433
  successCriteria: successCriteriaSchema.optional(),
2434
+ reportRequirements: SessionGoalReportRequirements.optional(),
2431
2435
  },
2432
2436
  },
2433
- async ({ text, successCriteria }) => {
2437
+ async ({ text, successCriteria, reportRequirements }) => {
2434
2438
  await authorizeFirstPartySession(deps, grant, sessionId, "session.goal.write");
2435
2439
  await requireSession(deps.db, grant.workspaceId, sessionId);
2436
2440
  const existing = await getSessionGoal(deps.db, grant.workspaceId, sessionId);
@@ -2444,15 +2448,24 @@ function registerGoalTools(
2444
2448
  ? (grant.metadata["turnId"] as string)
2445
2449
  : null;
2446
2450
  await assertGoalReactivationAllowed(deps, grant.workspaceId, sessionId, callerTurnId);
2451
+ const context = exactAgentCommandContext(grant, sessionId);
2447
2452
  const { goal, replaced, events } = await upsertSessionGoalWithEvent(deps.db, {
2448
2453
  accountId: grant.accountId,
2449
2454
  workspaceId: grant.workspaceId,
2450
2455
  sessionId,
2451
2456
  text,
2452
2457
  successCriteria: successCriteria ?? null,
2458
+ ...(reportRequirements !== undefined ? { reportRequirements } : {}),
2453
2459
  maxAutoContinuations: null,
2454
2460
  createdBy: "agent",
2455
2461
  actor: "agent",
2462
+ commandActor: {
2463
+ type: "agent_attempt",
2464
+ sessionId: context.callerSessionId,
2465
+ turnId: context.callerTurnId,
2466
+ attemptId: context.callerAttemptId,
2467
+ executionGeneration: context.callerExecutionGeneration,
2468
+ },
2456
2469
  });
2457
2470
  if (events.length > 0) {
2458
2471
  await deps.bus.publish(grant.workspaceId, sessionId, events);
@@ -2540,13 +2553,14 @@ function registerGoalTools(
2540
2553
  "goal_progress",
2541
2554
  {
2542
2555
  description:
2543
- "Record concrete progress toward the unchanged active goal. This does not change goal text, success criteria, mutation policy, or objective revision. Do not use it merely to keep the continuation loop alive.",
2556
+ "Record concrete progress toward the unchanged active goal. Optionally append reportRequirements for secondary user-facing reports discovered during other work; existing requirement IDs and titles cannot be changed or removed. This does not change goal text, success criteria, mutation policy, or objective revision. Do not use it merely to keep the continuation loop alive.",
2544
2557
  inputSchema: {
2545
2558
  progressNote: progressNoteSchema,
2546
2559
  idempotencyKey: z4.string().uuid(),
2560
+ reportRequirements: SessionGoalReportRequirements.optional(),
2547
2561
  },
2548
2562
  },
2549
- async ({ progressNote, idempotencyKey }) => {
2563
+ async ({ progressNote, idempotencyKey, reportRequirements }) => {
2550
2564
  await authorizeFirstPartySession(deps, grant, sessionId, "session.goal.write");
2551
2565
  const context = exactAgentCommandContext(grant, sessionId);
2552
2566
  const { goal, events, operationId, replay } = await recordSessionGoalProgressWithEvent(
@@ -2555,6 +2569,7 @@ function registerGoalTools(
2555
2569
  sessionId,
2556
2570
  {
2557
2571
  progressNote,
2572
+ ...(reportRequirements !== undefined ? { reportRequirements } : {}),
2558
2573
  command: {
2559
2574
  accountId: grant.accountId,
2560
2575
  actor: {
@@ -2635,16 +2650,23 @@ function registerGoalTools(
2635
2650
  "goal_complete",
2636
2651
  {
2637
2652
  description:
2638
- "Mark the session goal as completed. Requires concrete evidence (what was done and how it satisfies the success criteria). Completion prevents further continuation turns.",
2639
- inputSchema: { evidence: z4.string().min(1) },
2653
+ "Mark the session goal as completed with concrete evidence. Every persisted report requirement must have a matching reportDeliveries entry containing a native document artifactId and its server-issued inspectionReceiptId from a post-edit body inspection. Missing, stale, inaccessible or summary-only proof fails; inspect again after an edit. Omit reportDeliveries only when no reports were declared. Successful completion returns report artifact references and prevents further continuation turns.",
2654
+ inputSchema: {
2655
+ evidence: z4.string().min(1),
2656
+ reportDeliveries: SessionGoalReportDeliveries.optional(),
2657
+ },
2640
2658
  },
2641
- async ({ evidence }) => {
2659
+ async ({ evidence, reportDeliveries }) => {
2642
2660
  await authorizeFirstPartySession(deps, grant, sessionId, "session.goal.write");
2643
2661
  await requireSession(deps.db, grant.workspaceId, sessionId);
2644
2662
  const existing = await getSessionGoal(deps.db, grant.workspaceId, sessionId);
2645
2663
  if (!existing) {
2646
2664
  throw new Error("this session has no goal; use goal_set first");
2647
2665
  }
2666
+ const context = exactAgentCommandContext(grant, sessionId);
2667
+ const reportArtifactActor = editableArtifactActorForGrant(grant, "0000000000000001");
2668
+ if (reportArtifactActor.kind !== "agent")
2669
+ throw new Error("Goal completion requires exact agent authority");
2648
2670
  const { goal, events } = await setSessionGoalStatusWithEvent(
2649
2671
  deps.db,
2650
2672
  grant.workspaceId,
@@ -2652,6 +2674,15 @@ function registerGoalTools(
2652
2674
  {
2653
2675
  status: "completed",
2654
2676
  evidence,
2677
+ ...(reportDeliveries !== undefined ? { reportDeliveries } : {}),
2678
+ reportArtifactActor,
2679
+ commandActor: {
2680
+ type: "agent_attempt",
2681
+ sessionId: context.callerSessionId,
2682
+ turnId: context.callerTurnId,
2683
+ attemptId: context.callerAttemptId,
2684
+ executionGeneration: context.callerExecutionGeneration,
2685
+ },
2655
2686
  event: { type: "goal.completed", evidence },
2656
2687
  },
2657
2688
  );
@@ -2659,8 +2690,8 @@ function registerGoalTools(
2659
2690
  if (events.length > 0) {
2660
2691
  await deps.bus.publish(grant.workspaceId, sessionId, events);
2661
2692
  }
2662
- return json(
2663
- mcpMutationReceipt({
2693
+ return json({
2694
+ ...mcpMutationReceipt({
2664
2695
  operation: "goal_complete",
2665
2696
  committed: true,
2666
2697
  outcome: changed ? "updated" : "unchanged",
@@ -2675,7 +2706,13 @@ function registerGoalTools(
2675
2706
  idempotency: { status: "not_supported" },
2676
2707
  nextAction: { tool: "session_get", arguments: { sessionId } },
2677
2708
  }),
2678
- );
2709
+ reportDeliveries: SessionGoalReportDeliveries.parse(
2710
+ goal.metadata.reportDeliveriesV1 ?? [],
2711
+ ).map((delivery) => ({
2712
+ ...delivery,
2713
+ artifactReference: `[Open report](/workspaces/${grant.workspaceId}/artifacts/editable/${delivery.artifactId})`,
2714
+ })),
2715
+ });
2679
2716
  },
2680
2717
  );
2681
2718
 
@@ -708,6 +708,8 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
708
708
  ...(query.search ? { search: query.search } : {}),
709
709
  ...(query.pinsOnly ? { pinsOnly: true } : {}),
710
710
  ...(query.archivedOnly ? { archivedOnly: true } : {}),
711
+ ...(query.sortBy ? { sortBy: query.sortBy } : {}),
712
+ ...(query.archiveStatus ? { archiveStatus: query.archiveStatus } : {}),
711
713
  ...(query.parentSessionId !== undefined ? { parentSessionId: query.parentSessionId } : {}),
712
714
  ...(query.channelId !== undefined ? { channelId: query.channelId } : {}),
713
715
  ...(query.originSiteId ? { originSiteId: query.originSiteId } : {}),
@@ -2569,10 +2571,17 @@ export function registerSessionRoutes(app: Hono, deps: SessionRouteDeps): void {
2569
2571
 
2570
2572
  app.delete("/v1/workspaces/:workspaceId/sessions/:sessionId/goal", async (c) => {
2571
2573
  const workspaceId = c.req.param("workspaceId");
2572
- await requireAccessGrant(c, deps, workspaceId, "sessions:control");
2574
+ const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
2573
2575
  const sessionId = c.req.param("sessionId");
2574
2576
  await assertSessionExists(db, workspaceId, sessionId);
2575
- const { event } = await clearSessionGoal(db, workspaceId, sessionId);
2577
+ const { event } = await clearSessionGoal(db, workspaceId, sessionId, {
2578
+ actor: grant.principalKind === "agent_attempt" ? "agent" : "api",
2579
+ }).catch((error: unknown) => {
2580
+ if (error instanceof SessionControlConflictError) {
2581
+ throw new HTTPException(409, { message: error.message, cause: error });
2582
+ }
2583
+ throw error;
2584
+ });
2576
2585
  if (event) {
2577
2586
  try {
2578
2587
  await bus.publish(workspaceId, sessionId, [event]);
@@ -4795,7 +4804,7 @@ function eventEnumList<T extends string>(
4795
4804
  });
4796
4805
  }
4797
4806
 
4798
- function sessionListQuery(
4807
+ export function sessionListQuery(
4799
4808
  query: Record<string, string>,
4800
4809
  allowCursor = true,
4801
4810
  ): {
@@ -4806,6 +4815,8 @@ function sessionListQuery(
4806
4815
  search: string | undefined;
4807
4816
  pinsOnly: boolean;
4808
4817
  archivedOnly: boolean;
4818
+ sortBy: "updatedAt" | "createdAt" | "name" | undefined;
4819
+ archiveStatus: "active" | "archived" | "all" | undefined;
4809
4820
  channelId: string | null | undefined;
4810
4821
  createdBy: { kind: "subject" | "service"; subjectId: string } | undefined;
4811
4822
  updatedFrom: Date | undefined;
@@ -4852,6 +4863,20 @@ function sessionListQuery(
4852
4863
  throw new HTTPException(400, { message: 'archivedOnly must be the literal "true"' });
4853
4864
  }
4854
4865
  const archivedOnly = query.archivedOnly === "true";
4866
+ const sortBy = z.enum(["updatedAt", "createdAt", "name"]).optional().safeParse(query.sortBy);
4867
+ const archiveStatus = z
4868
+ .enum(["active", "archived", "all"])
4869
+ .optional()
4870
+ .safeParse(query.archiveStatus);
4871
+ if (!sortBy.success || !archiveStatus.success) {
4872
+ throw new HTTPException(400, { message: "Invalid session sortBy or archiveStatus" });
4873
+ }
4874
+ if (archivedOnly && archiveStatus.data !== undefined && archiveStatus.data !== "archived") {
4875
+ throw new HTTPException(400, { message: "archivedOnly conflicts with archiveStatus" });
4876
+ }
4877
+ if (pinsOnly && archiveStatus.data === "archived") {
4878
+ throw new HTTPException(400, { message: "pinsOnly cannot be combined with archived status" });
4879
+ }
4855
4880
  const channelId = query.channelId;
4856
4881
  if (
4857
4882
  channelId !== undefined &&
@@ -4956,6 +4981,8 @@ function sessionListQuery(
4956
4981
  search: search || undefined,
4957
4982
  pinsOnly,
4958
4983
  archivedOnly,
4984
+ sortBy: sortBy.data,
4985
+ archiveStatus: archiveStatus.data,
4959
4986
  channelId: channelId === undefined ? undefined : channelId === "null" ? null : channelId,
4960
4987
  createdBy:
4961
4988
  createdByKind && createdBySubjectId