@opengeni/api-router 0.5.4 → 0.5.5

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.
@@ -4,7 +4,12 @@ import {
4
4
  configuredAllowedReasoningEfforts,
5
5
  configuredModels
6
6
  } from "@opengeni/config";
7
- import { ClientConfig, resolveWorkspaceMemoryEnabled } from "@opengeni/contracts";
7
+ import {
8
+ ClientConfig,
9
+ OPENGENI_API_CONTRACT_HEADER,
10
+ OPENGENI_API_CONTRACT_REVISION,
11
+ resolveWorkspaceMemoryEnabled
12
+ } from "@opengeni/contracts";
8
13
  import {
9
14
  createDocumentServices,
10
15
  indexDocumentNow
@@ -14,6 +19,7 @@ import { createObservability } from "@opengeni/observability";
14
19
  import { createObjectStorage } from "@opengeni/storage";
15
20
  import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
16
21
  import { Hono } from "hono";
22
+ import { bodyLimit } from "hono/body-limit";
17
23
  import { cors } from "hono/cors";
18
24
  import { HTTPException as HTTPException24 } from "hono/http-exception";
19
25
  import { hasPermission as hasPermission4, requireAccessGrant as requireAccessGrant17, requirePermission } from "@opengeni/core";
@@ -279,7 +285,6 @@ import {
279
285
  UpdateScheduledTaskRequest
280
286
  } from "@opengeni/contracts";
281
287
  import {
282
- addSessionSystemUpdate,
283
288
  correctWorkspaceMemory,
284
289
  countVariableSets,
285
290
  beginRigChangeVerificationAttempt,
@@ -288,6 +293,7 @@ import {
288
293
  encryptVariableSetValue,
289
294
  getSession,
290
295
  getSessionGoal,
296
+ getSessionQueueSnapshot,
291
297
  getSessionTurn,
292
298
  getVariableSet,
293
299
  getVariableSetByName,
@@ -306,10 +312,8 @@ import {
306
312
  requireFile,
307
313
  requireScheduledTask,
308
314
  requireSession,
309
- requestSessionControl,
310
315
  saveWorkspaceMemory,
311
316
  searchWorkspaceMemories,
312
- setSessionChildNotificationsMode,
313
317
  setSessionGoalStatus,
314
318
  setVariableSetVariable,
315
319
  updateScheduledTask,
@@ -356,9 +360,12 @@ import {
356
360
  } from "@opengeni/core";
357
361
  import {
358
362
  acceptSessionUserMessage,
363
+ controlAgentSessionWorkstream,
364
+ controlHumanSessionWorkstream,
359
365
  createSessionForRequest,
360
- updateSessionTitle,
361
- workflowIdForSession
366
+ sendAgentSessionMessage,
367
+ steerAgentSession,
368
+ updateSessionTitle
362
369
  } from "@opengeni/core";
363
370
  import {
364
371
  buildFleetContextForSession,
@@ -545,24 +552,6 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
545
552
  }
546
553
  );
547
554
  }
548
- if (sessionId !== null && !toolspaceMode && can("sessions:create")) {
549
- server.registerTool(
550
- "set_child_notifications_mode",
551
- {
552
- description: "Change how workers you spawn report back when they finish. This setting persists across turns and must not be re-applied as routine setup or recovery. 'digest' (default): completions arrive as a coalesced turn you process. 'passive': completions appear only as quiet cards and never queue a turn or model run. Call only when the desired mode differs from the mode already in effect.",
553
- inputSchema: { mode: z4.enum(["digest", "passive"]) }
554
- },
555
- async ({ mode }) => {
556
- const changed = await setSessionChildNotificationsMode(
557
- deps.db,
558
- grant.workspaceId,
559
- sessionId,
560
- mode
561
- );
562
- return json({ ok: true, changed, mode });
563
- }
564
- );
565
- }
566
555
  if (sessionId !== null && can("goals:manage")) {
567
556
  registerGoalTools(server, deps, grant, sessionId, json);
568
557
  }
@@ -575,7 +564,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
575
564
  if (!toolspaceMode) {
576
565
  registerRigTools(server, deps, grant, can, sessionId, json);
577
566
  }
578
- registerWorkspaceOrchestrationTools(server, deps, grant, can, sessionId, json);
567
+ registerWorkspaceOrchestrationTools(server, deps, grant, can, sessionId, toolspaceMode, json);
579
568
  registerVariableSetTools(server, deps, grant, can, json);
580
569
  if (can("github:use")) {
581
570
  registerGitHubConnectTool(server, deps, grant, options, json);
@@ -1445,7 +1434,23 @@ function registerRigTools(server, deps, grant, can, sessionId, json) {
1445
1434
  );
1446
1435
  }
1447
1436
  }
1448
- function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSessionId, json) {
1437
+ function exactAgentCommandContext(grant, callerSessionId) {
1438
+ const turnId = grant.metadata?.["turnId"];
1439
+ const attemptId = grant.metadata?.["attemptId"];
1440
+ const executionGeneration = grant.metadata?.["executionGeneration"];
1441
+ if (typeof turnId !== "string" || typeof attemptId !== "string" || typeof executionGeneration !== "number" || !Number.isSafeInteger(executionGeneration) || executionGeneration < 1) {
1442
+ throw new Error("caller_attempt_claims_missing");
1443
+ }
1444
+ return {
1445
+ accountId: grant.accountId,
1446
+ workspaceId: grant.workspaceId,
1447
+ callerSessionId,
1448
+ callerTurnId: turnId,
1449
+ callerAttemptId: attemptId,
1450
+ callerExecutionGeneration: executionGeneration
1451
+ };
1452
+ }
1453
+ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSessionId, toolspaceMode, json) {
1449
1454
  if (can("sessions:read")) {
1450
1455
  server.registerTool(
1451
1456
  "sessions_list",
@@ -1466,7 +1471,11 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
1466
1471
  if (!session) {
1467
1472
  throw new Error("session not found");
1468
1473
  }
1469
- return json(capSessionDetail(session));
1474
+ const queue = await getSessionQueueSnapshot(deps.db, grant.workspaceId, sessionId);
1475
+ return json({
1476
+ ...capSessionDetail(session),
1477
+ effectiveControl: queue?.effectiveControl ?? null
1478
+ });
1470
1479
  }
1471
1480
  );
1472
1481
  server.registerTool(
@@ -1576,7 +1585,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
1576
1585
  async (args) => json(await createSessionForRequest(deps, grant, grant.workspaceId, args))
1577
1586
  );
1578
1587
  }
1579
- if (can("sessions:control")) {
1588
+ if (can("sessions:control") && !toolspaceMode) {
1580
1589
  server.registerTool(
1581
1590
  "session_send_message",
1582
1591
  {
@@ -1584,50 +1593,30 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
1584
1593
  inputSchema: {
1585
1594
  sessionId: z4.string().uuid(),
1586
1595
  text: z4.string().min(1),
1596
+ idempotencyKey: z4.string().uuid(),
1587
1597
  // Header-value rotation only. URL/name/tool settings are immutable
1588
1598
  // after create; core enforces mcp_servers:attach on this field.
1589
1599
  mcpCredentialUpdates: z4.array(z4.unknown()).optional()
1590
1600
  }
1591
1601
  },
1592
- async ({ sessionId: targetSessionId, text, mcpCredentialUpdates }) => {
1602
+ async ({ sessionId: targetSessionId, text, idempotencyKey, mcpCredentialUpdates }) => {
1593
1603
  if (callerSessionId !== null) {
1594
1604
  if ((mcpCredentialUpdates?.length ?? 0) > 0) {
1595
1605
  throw new Error("internal session updates cannot change MCP credentials");
1596
1606
  }
1597
- const result = await addSessionSystemUpdate(deps.db, {
1598
- accountId: grant.accountId,
1599
- workspaceId: grant.workspaceId,
1600
- sessionId: targetSessionId,
1601
- kind: "runtime_notice",
1602
- classification: "info",
1603
- sourceId: callerSessionId,
1604
- dedupeKey: `session-message:${callerSessionId}:${crypto.randomUUID()}`,
1605
- summary: text,
1606
- payload: { text },
1607
- lineage: { sourceSessionId: callerSessionId, targetSessionId }
1608
- });
1609
- if (result.reason === "session_cancelled") {
1610
- return json({ delivered: false, reason: result.reason });
1611
- }
1612
- if (result.added && result.events.length > 0) {
1613
- await deps.bus.publish(grant.workspaceId, targetSessionId, result.events);
1614
- }
1615
- if (result.shouldWake) {
1616
- if (result.workflowWakeRevision === null) {
1617
- throw new Error("Internal update has no workflow wake revision");
1618
- }
1619
- await deps.workflowClient.wakeSessionWorkflow({
1620
- accountId: grant.accountId,
1621
- workspaceId: grant.workspaceId,
1622
- sessionId: targetSessionId,
1623
- workflowId: result.temporalWorkflowId ?? workflowIdForSession(targetSessionId),
1624
- wakeRevision: result.workflowWakeRevision
1625
- });
1626
- }
1607
+ const result = await sendAgentSessionMessage(
1608
+ deps,
1609
+ exactAgentCommandContext(grant, callerSessionId),
1610
+ { targetSessionId, text, idempotencyKey }
1611
+ );
1627
1612
  return json({
1628
1613
  delivered: true,
1629
- updateId: result.update.id,
1630
- delivery: "coalesced_internal_update"
1614
+ updateId: result.updateId,
1615
+ delivery: "coalesced_internal_update",
1616
+ effectiveState: result.effectiveState,
1617
+ wakeRequested: result.wakeRevision !== null,
1618
+ resumeRequired: result.effectiveState === "paused",
1619
+ replay: result.replay
1631
1620
  });
1632
1621
  }
1633
1622
  const { accepted, turn } = await acceptSessionUserMessage(
@@ -1638,8 +1627,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
1638
1627
  {
1639
1628
  text,
1640
1629
  toolsProvided: false,
1641
- delivery: "queue",
1630
+ delivery: "send",
1642
1631
  origin: "operator",
1632
+ clientEventId: idempotencyKey,
1643
1633
  mcpCredentialUpdates: (mcpCredentialUpdates ?? []).map(
1644
1634
  (update) => SessionMcpCredentialUpdateInput.parse(update)
1645
1635
  )
@@ -1653,39 +1643,122 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
1653
1643
  {
1654
1644
  description: "Pause this session. Waiting prompts stay saved and inert until Resume.",
1655
1645
  inputSchema: {
1656
- sessionId: z4.string().uuid()
1646
+ sessionId: z4.string().uuid(),
1647
+ idempotencyKey: z4.string().uuid(),
1648
+ reason: z4.string().min(1).max(500).optional()
1657
1649
  }
1658
1650
  },
1659
- async ({ sessionId }) => {
1660
- const controlled = await requestSessionControl(deps.db, {
1661
- accountId: grant.accountId,
1662
- workspaceId: grant.workspaceId,
1663
- sessionId,
1664
- actor: grant.subjectId,
1665
- mode: "pause",
1666
- reason: "mcp_pause"
1667
- });
1668
- await deps.bus.publish(grant.workspaceId, sessionId, controlled.events);
1669
- if (controlled.shouldSignalControl) {
1670
- if (controlled.workflowWakeRevision === null) {
1671
- throw new Error("Session control has no workflow wake revision");
1672
- }
1673
- await deps.workflowClient.signalSessionControl({
1674
- accountId: grant.accountId,
1675
- workspaceId: grant.workspaceId,
1676
- sessionId,
1677
- eventId: controlled.event.id,
1678
- workflowId: workflowIdForSession(sessionId),
1679
- workflowWakeRevision: controlled.workflowWakeRevision
1651
+ async ({ sessionId, idempotencyKey, reason }) => {
1652
+ if (callerSessionId !== null) {
1653
+ const controlled = await controlAgentSessionWorkstream(
1654
+ deps,
1655
+ exactAgentCommandContext(grant, callerSessionId),
1656
+ {
1657
+ targetSessionId: sessionId,
1658
+ action: "pause",
1659
+ idempotencyKey,
1660
+ reason: reason ?? "agent_mcp_pause"
1661
+ }
1662
+ );
1663
+ return json({
1664
+ receiptId: controlled.receipt.id,
1665
+ effectiveControl: controlled.control,
1666
+ interruptionCount: controlled.interruptionCount,
1667
+ replay: controlled.replay
1680
1668
  });
1681
1669
  }
1682
- return json({
1683
- event: controlled.event,
1684
- controlState: controlled.controlState,
1685
- controlGeneration: controlled.controlGeneration
1686
- });
1670
+ return json(
1671
+ await controlHumanSessionWorkstream(
1672
+ deps,
1673
+ {
1674
+ accountId: grant.accountId,
1675
+ workspaceId: grant.workspaceId,
1676
+ sessionId,
1677
+ subjectId: grant.subjectId
1678
+ },
1679
+ {
1680
+ action: "pause",
1681
+ clientEventId: idempotencyKey,
1682
+ ...reason ? { reason } : {}
1683
+ }
1684
+ )
1685
+ );
1686
+ }
1687
+ );
1688
+ server.registerTool(
1689
+ "session_resume",
1690
+ {
1691
+ description: "Resume the selected session workstream through older parent/workspace pauses. This creates no message.",
1692
+ inputSchema: {
1693
+ sessionId: z4.string().uuid(),
1694
+ idempotencyKey: z4.string().uuid(),
1695
+ reason: z4.string().min(1).max(500).optional()
1696
+ }
1697
+ },
1698
+ async ({ sessionId, idempotencyKey, reason }) => {
1699
+ if (callerSessionId !== null) {
1700
+ const controlled = await controlAgentSessionWorkstream(
1701
+ deps,
1702
+ exactAgentCommandContext(grant, callerSessionId),
1703
+ {
1704
+ targetSessionId: sessionId,
1705
+ action: "resume",
1706
+ idempotencyKey,
1707
+ reason: reason ?? "agent_mcp_resume"
1708
+ }
1709
+ );
1710
+ return json({
1711
+ receiptId: controlled.receipt.id,
1712
+ effectiveControl: controlled.control,
1713
+ interruptionCount: controlled.interruptionCount,
1714
+ replay: controlled.replay
1715
+ });
1716
+ }
1717
+ return json(
1718
+ await controlHumanSessionWorkstream(
1719
+ deps,
1720
+ {
1721
+ accountId: grant.accountId,
1722
+ workspaceId: grant.workspaceId,
1723
+ sessionId,
1724
+ subjectId: grant.subjectId
1725
+ },
1726
+ {
1727
+ action: "resume",
1728
+ clientEventId: idempotencyKey,
1729
+ ...reason ? { reason } : {}
1730
+ }
1731
+ )
1732
+ );
1687
1733
  }
1688
1734
  );
1735
+ if (callerSessionId !== null) {
1736
+ server.registerTool(
1737
+ "session_steer",
1738
+ {
1739
+ description: "Atomically replace another session's current direction and resume it. The instruction is an internal update, never a human queue row.",
1740
+ inputSchema: {
1741
+ sessionId: z4.string().uuid(),
1742
+ instruction: z4.string().min(1),
1743
+ idempotencyKey: z4.string().uuid()
1744
+ }
1745
+ },
1746
+ async ({ sessionId, instruction, idempotencyKey }) => {
1747
+ const result = await steerAgentSession(
1748
+ deps,
1749
+ exactAgentCommandContext(grant, callerSessionId),
1750
+ { targetSessionId: sessionId, instruction, idempotencyKey }
1751
+ );
1752
+ return json({
1753
+ updateId: result.updateId,
1754
+ interruptionCount: result.interruptionCount,
1755
+ stoppingPreviousAttempt: result.interruptionCount > 0,
1756
+ effectiveState: result.effectiveState,
1757
+ replay: result.replay
1758
+ });
1759
+ }
1760
+ );
1761
+ }
1689
1762
  server.registerTool(
1690
1763
  "set_other_session_title",
1691
1764
  {
@@ -7831,9 +7904,10 @@ import {
7831
7904
  AcknowledgeStreamRequest,
7832
7905
  AttachViewerRequest,
7833
7906
  ClearSessionContextRequest,
7834
- CancelSessionQueueItemRequest,
7835
7907
  ClientSessionEvent,
7836
7908
  CompactSessionContextRequest,
7909
+ DeleteSessionQueueItemRequest,
7910
+ EditSessionQueueItemRequest,
7837
7911
  FsDeleteRequest,
7838
7912
  FsListRequest,
7839
7913
  FsMkdirRequest,
@@ -7844,11 +7918,14 @@ import {
7844
7918
  GitLogRequest,
7845
7919
  GitShowRequest,
7846
7920
  GitStatusRequest,
7921
+ MoveSessionQueueItemRequest,
7847
7922
  PtyCloseRequest,
7848
7923
  PtyOpenRequest,
7849
7924
  PtyResizeRequest,
7850
7925
  PtyWriteRequest,
7851
7926
  SessionControlRequest,
7927
+ SaveComposerDraftRequest,
7928
+ SteerSessionQueueItemRequest,
7852
7929
  SteerSessionMessageRequest,
7853
7930
  TerminalExecRequest,
7854
7931
  UpdateSessionPinRequest,
@@ -7858,7 +7935,6 @@ import {
7858
7935
  } from "@opengeni/contracts";
7859
7936
  import { streamTokenDegraded } from "@opengeni/config";
7860
7937
  import {
7861
- cancelQueuedSessionTurnWithVersion,
7862
7938
  acceptSessionApprovalDecision,
7863
7939
  clearSessionGoal,
7864
7940
  clearSessionContext,
@@ -7868,7 +7944,7 @@ import {
7868
7944
  getSession as getSession4,
7869
7945
  getSessionForSubject,
7870
7946
  getSessionGoal as getSessionGoal2,
7871
- getSessionQueueSnapshot,
7947
+ getSessionQueueSnapshot as getSessionQueueSnapshot2,
7872
7948
  getStreamAcknowledgment,
7873
7949
  insertPtySession,
7874
7950
  listSessionEvents as listSessionEvents3,
@@ -7888,8 +7964,9 @@ import {
7888
7964
  revokeViewer,
7889
7965
  setSessionGoalStatus as setSessionGoalStatus2,
7890
7966
  updatePtySessionActivity,
7891
- requestSessionControl as requestSessionControl2,
7892
- SessionQueueConflictError,
7967
+ QueueCommandConflictError,
7968
+ SessionCommandIdempotencyError,
7969
+ SessionControlConflictError,
7893
7970
  SessionContextBusyError,
7894
7971
  latestWorkspaceCapture,
7895
7972
  workspaceCaptureAtRevision
@@ -8681,14 +8758,21 @@ function viewerIdAsUuid(rawViewerId) {
8681
8758
  // src/routes/sessions.ts
8682
8759
  import {
8683
8760
  acceptSessionUserMessage as acceptSessionUserMessage2,
8761
+ controlHumanSessionWorkstream as controlHumanSessionWorkstream2,
8684
8762
  createSessionForRequest as createSessionForRequest2,
8763
+ deleteHumanQueuePrompt,
8764
+ editHumanQueuePrompt,
8765
+ getHumanComposerDraft,
8766
+ moveHumanQueuePrompt,
8685
8767
  readSessionLineage,
8768
+ saveHumanComposerDraft,
8769
+ steerHumanQueuePrompt,
8686
8770
  updateSessionTitle as updateSessionTitle2,
8687
- workflowIdForSession as workflowIdForSession2
8771
+ workflowIdForSession
8688
8772
  } from "@opengeni/core";
8689
8773
 
8690
8774
  // src/http/sse.ts
8691
- import { listSessionEvents as listSessionEvents2 } from "@opengeni/db";
8775
+ import { listSessionEvents as listSessionEvents2, listWorkspaceControlEvents } from "@opengeni/db";
8692
8776
  import { formatSse } from "@opengeni/events";
8693
8777
  async function sseSessionStream(db, bus, workspaceId, sessionId, after, signal) {
8694
8778
  const encoder = new TextEncoder();
@@ -8778,6 +8862,53 @@ async function replaySessionEvents(loadPage, send, after, pageSize = 1e3) {
8778
8862
  }
8779
8863
  }
8780
8864
  }
8865
+ async function sseWorkspaceControlStream(db, bus, workspaceId, after, signal) {
8866
+ const encoder = new TextEncoder();
8867
+ let lastSent = after;
8868
+ let replaying = true;
8869
+ const buffered = [];
8870
+ let unsubscribe = null;
8871
+ const stream = new ReadableStream({
8872
+ start: async (controller) => {
8873
+ const send = (event) => {
8874
+ if (event.sequence <= lastSent) return;
8875
+ controller.enqueue(encoder.encode(formatSse(event)));
8876
+ lastSent = event.sequence;
8877
+ };
8878
+ unsubscribe = await bus.subscribeWorkspaceControl(workspaceId, async (event) => {
8879
+ if (replaying) {
8880
+ buffered.push(event);
8881
+ } else {
8882
+ send(event);
8883
+ }
8884
+ });
8885
+ let cursor = after;
8886
+ while (true) {
8887
+ const page = await listWorkspaceControlEvents(db, workspaceId, cursor, 1e3);
8888
+ for (const event of page) {
8889
+ send(event);
8890
+ cursor = Math.max(cursor, event.sequence);
8891
+ }
8892
+ if (page.length < 1e3) break;
8893
+ }
8894
+ replaying = false;
8895
+ for (const event of buffered.sort((left, right) => left.sequence - right.sequence)) {
8896
+ send(event);
8897
+ }
8898
+ buffered.length = 0;
8899
+ controller.enqueue(encoder.encode(": connected\n\n"));
8900
+ },
8901
+ cancel: () => unsubscribe?.()
8902
+ });
8903
+ signal.addEventListener("abort", () => unsubscribe?.(), { once: true });
8904
+ return new Response(stream, {
8905
+ headers: {
8906
+ "Content-Type": "text/event-stream; charset=utf-8",
8907
+ "Cache-Control": "no-cache, no-transform",
8908
+ Connection: "keep-alive"
8909
+ }
8910
+ });
8911
+ }
8781
8912
 
8782
8913
  // src/routes/workspace-capture.ts
8783
8914
  import {
@@ -9170,7 +9301,7 @@ function registerSessionRoutes(app, deps) {
9170
9301
  accountId: grant.accountId,
9171
9302
  workspaceId,
9172
9303
  sessionId,
9173
- workflowId: workflowIdForSession2(sessionId),
9304
+ workflowId: workflowIdForSession(sessionId),
9174
9305
  wakeRevision: workflowWakeRevision
9175
9306
  });
9176
9307
  }
@@ -9288,111 +9419,133 @@ function registerSessionRoutes(app, deps) {
9288
9419
  const workspaceId = c.req.param("workspaceId");
9289
9420
  await requireAccessGrant14(c, deps, workspaceId, "sessions:read");
9290
9421
  const sessionId = c.req.param("sessionId");
9291
- const snapshot = await getSessionQueueSnapshot(db, workspaceId, sessionId);
9422
+ const snapshot = await getSessionQueueSnapshot2(db, workspaceId, sessionId);
9292
9423
  if (!snapshot) throw new HTTPException21(404, { message: "session not found" });
9293
9424
  return c.json(snapshot);
9294
9425
  });
9295
- app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/cancel", async (c) => {
9426
+ app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/move", async (c) => {
9296
9427
  const workspaceId = c.req.param("workspaceId");
9297
9428
  const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
9298
9429
  const sessionId = c.req.param("sessionId");
9299
9430
  await assertSessionExists(db, workspaceId, sessionId);
9300
- const payload = CancelSessionQueueItemRequest.parse(await c.req.json());
9431
+ const payload = MoveSessionQueueItemRequest.parse(await c.req.json());
9301
9432
  try {
9302
- const result = await cancelQueuedSessionTurnWithVersion(
9303
- db,
9304
- workspaceId,
9305
- sessionId,
9306
- c.req.param("turnId"),
9307
- payload.expectedQueueVersion,
9308
- payload.expectedItemVersion,
9309
- grant.subjectId,
9310
- payload.reason ?? null
9433
+ return c.json(
9434
+ await moveHumanQueuePrompt(
9435
+ { db, bus },
9436
+ { accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
9437
+ c.req.param("turnId"),
9438
+ payload
9439
+ )
9311
9440
  );
9312
- await bus.publish(workspaceId, sessionId, result.events);
9313
- if (result.shouldWake) {
9314
- if (result.workflowWakeRevision === null) {
9315
- throw new Error("Queue continuation has no workflow wake revision");
9316
- }
9317
- await workflowClient.wakeSessionWorkflow({
9318
- accountId: grant.accountId,
9319
- workspaceId,
9320
- sessionId,
9321
- workflowId: workflowIdForSession2(sessionId),
9322
- wakeRevision: result.workflowWakeRevision
9323
- });
9324
- }
9325
- return c.json(result);
9326
9441
  } catch (error) {
9327
- throwQueueConflict(error);
9442
+ return commandConflictResponse(c, error);
9328
9443
  }
9329
9444
  });
9330
- app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/control", async (c) => {
9445
+ app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/edit", async (c) => {
9331
9446
  const workspaceId = c.req.param("workspaceId");
9332
9447
  const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
9333
9448
  const sessionId = c.req.param("sessionId");
9334
- const payload = SessionControlRequest.parse(await c.req.json());
9335
- let result;
9449
+ await assertSessionExists(db, workspaceId, sessionId);
9450
+ const payload = EditSessionQueueItemRequest.parse(await c.req.json());
9336
9451
  try {
9337
- result = await requestSessionControl2(db, {
9338
- accountId: grant.accountId,
9339
- workspaceId,
9340
- sessionId,
9341
- actor: grant.subjectId,
9342
- mode: payload.mode,
9343
- reason: payload.reason ?? null,
9344
- clientEventId: payload.clientEventId ?? null,
9345
- ...payload.expectedControlState !== void 0 ? { expectedControlState: payload.expectedControlState } : {},
9346
- ...payload.expectedControlGeneration !== void 0 ? { expectedControlGeneration: payload.expectedControlGeneration } : {},
9347
- ...payload.expectedWorkspaceInferenceGeneration !== void 0 ? {
9348
- expectedWorkspaceInferenceGeneration: payload.expectedWorkspaceInferenceGeneration
9349
- } : {}
9350
- });
9452
+ return c.json(
9453
+ await editHumanQueuePrompt(
9454
+ { db, bus },
9455
+ { accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
9456
+ c.req.param("turnId"),
9457
+ payload
9458
+ )
9459
+ );
9351
9460
  } catch (error) {
9352
- throwQueueConflict(error);
9461
+ return commandConflictResponse(c, error);
9353
9462
  }
9354
- await bus.publish(workspaceId, sessionId, result.events);
9355
- const workflowId = workflowIdForSession2(sessionId);
9356
- if (result.shouldSignalControl) {
9357
- if (result.workflowWakeRevision === null) {
9358
- throw new Error("Session control has no workflow wake revision");
9359
- }
9360
- await workflowClient.signalSessionControl({
9361
- accountId: grant.accountId,
9362
- workspaceId,
9363
- sessionId,
9364
- eventId: result.event.id,
9365
- workflowId,
9366
- workflowWakeRevision: result.workflowWakeRevision
9367
- });
9368
- } else if (result.shouldWake) {
9369
- if (result.workflowWakeRevision === null) {
9370
- throw new Error("Session resume has no workflow wake revision");
9371
- }
9372
- await workflowClient.wakeSessionWorkflow({
9463
+ });
9464
+ app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/steer", async (c) => {
9465
+ const workspaceId = c.req.param("workspaceId");
9466
+ const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
9467
+ const sessionId = c.req.param("sessionId");
9468
+ await assertSessionExists(db, workspaceId, sessionId);
9469
+ const payload = SteerSessionQueueItemRequest.parse(await c.req.json());
9470
+ try {
9471
+ return c.json(
9472
+ await steerHumanQueuePrompt(
9473
+ { db, bus },
9474
+ { accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
9475
+ c.req.param("turnId"),
9476
+ payload
9477
+ )
9478
+ );
9479
+ } catch (error) {
9480
+ return commandConflictResponse(c, error);
9481
+ }
9482
+ });
9483
+ app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/delete", async (c) => {
9484
+ const workspaceId = c.req.param("workspaceId");
9485
+ const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
9486
+ const sessionId = c.req.param("sessionId");
9487
+ await assertSessionExists(db, workspaceId, sessionId);
9488
+ const payload = DeleteSessionQueueItemRequest.parse(await c.req.json());
9489
+ try {
9490
+ return c.json(
9491
+ await deleteHumanQueuePrompt(
9492
+ { db, bus },
9493
+ { accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
9494
+ c.req.param("turnId"),
9495
+ payload
9496
+ )
9497
+ );
9498
+ } catch (error) {
9499
+ return commandConflictResponse(c, error);
9500
+ }
9501
+ });
9502
+ app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/composer-draft", async (c) => {
9503
+ const workspaceId = c.req.param("workspaceId");
9504
+ const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:read");
9505
+ const sessionId = c.req.param("sessionId");
9506
+ return c.json(
9507
+ await getHumanComposerDraft(db, {
9373
9508
  accountId: grant.accountId,
9374
9509
  workspaceId,
9375
9510
  sessionId,
9376
- workflowId,
9377
- wakeRevision: result.workflowWakeRevision
9378
- });
9379
- }
9380
- return c.json(
9381
- {
9382
- operationId: result.operationId,
9383
- event: result.event,
9384
- controlState: result.controlState,
9385
- controlGeneration: result.controlGeneration,
9386
- expectedActiveTurnId: result.expectedActiveTurnId,
9387
- expectedExecutionGeneration: result.expectedExecutionGeneration,
9388
- expectedAttemptId: result.expectedAttemptId,
9389
- deliveryEventId: result.deliveryEventId,
9390
- shouldSignalControl: result.shouldSignalControl,
9391
- shouldWake: result.shouldWake
9392
- },
9393
- 202
9511
+ subjectId: grant.subjectId
9512
+ })
9394
9513
  );
9395
9514
  });
9515
+ app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/composer-draft", async (c) => {
9516
+ const workspaceId = c.req.param("workspaceId");
9517
+ const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
9518
+ const sessionId = c.req.param("sessionId");
9519
+ const payload = SaveComposerDraftRequest.parse(await c.req.json());
9520
+ try {
9521
+ return c.json(
9522
+ await saveHumanComposerDraft(
9523
+ db,
9524
+ { accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
9525
+ payload
9526
+ )
9527
+ );
9528
+ } catch (error) {
9529
+ return commandConflictResponse(c, error);
9530
+ }
9531
+ });
9532
+ app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/control", async (c) => {
9533
+ const workspaceId = c.req.param("workspaceId");
9534
+ const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
9535
+ const sessionId = c.req.param("sessionId");
9536
+ const payload = SessionControlRequest.parse(await c.req.json());
9537
+ try {
9538
+ return c.json(
9539
+ await controlHumanSessionWorkstream2(
9540
+ { db, bus, workflowClient },
9541
+ { accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
9542
+ payload
9543
+ )
9544
+ );
9545
+ } catch (error) {
9546
+ return commandConflictResponse(c, error);
9547
+ }
9548
+ });
9396
9549
  app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/steer", async (c) => {
9397
9550
  const workspaceId = c.req.param("workspaceId");
9398
9551
  const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:control");
@@ -9410,10 +9563,8 @@ function registerSessionRoutes(app, deps) {
9410
9563
  mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
9411
9564
  delivery: "steer",
9412
9565
  origin: "human",
9413
- ...payload.expectedControlGeneration !== void 0 ? { expectedControlGeneration: payload.expectedControlGeneration } : {},
9414
- ...payload.expectedWorkspaceInferenceGeneration !== void 0 ? {
9415
- expectedWorkspaceInferenceGeneration: payload.expectedWorkspaceInferenceGeneration
9416
- } : {},
9566
+ ...payload.controlEtag !== void 0 ? { controlEtag: payload.controlEtag } : {},
9567
+ ...payload.expectedDraftRevision !== void 0 ? { expectedDraftRevision: payload.expectedDraftRevision } : {},
9417
9568
  ...payload.clientEventId ? { clientEventId: payload.clientEventId } : {}
9418
9569
  });
9419
9570
  return c.json(result, 202);
@@ -9433,6 +9584,8 @@ function registerSessionRoutes(app, deps) {
9433
9584
  model: event.payload.model ?? null,
9434
9585
  reasoningEffort: event.payload.reasoningEffort ?? null,
9435
9586
  mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
9587
+ ...event.payload.controlEtag !== void 0 ? { controlEtag: event.payload.controlEtag } : {},
9588
+ ...event.payload.expectedDraftRevision !== void 0 ? { expectedDraftRevision: event.payload.expectedDraftRevision } : {},
9436
9589
  ...event.clientEventId ? { clientEventId: event.clientEventId } : {}
9437
9590
  });
9438
9591
  return c.json(accepted, 202);
@@ -9451,7 +9604,7 @@ function registerSessionRoutes(app, deps) {
9451
9604
  });
9452
9605
  }
9453
9606
  await publishDurableSessionEvents(bus, workspaceId, sessionId, accepted.events);
9454
- const workflowId = workflowIdForSession2(sessionId);
9607
+ const workflowId = workflowIdForSession(sessionId);
9455
9608
  await workflowClient.signalApprovalDecision({
9456
9609
  accountId: grant.accountId,
9457
9610
  workspaceId,
@@ -10172,15 +10325,15 @@ function userMessagePayloadHasOwnProperty(value, key) {
10172
10325
  const payload = value.payload;
10173
10326
  return hasOwnProperty(payload, key);
10174
10327
  }
10175
- function throwQueueConflict(error) {
10176
- if (error instanceof SessionQueueConflictError) {
10177
- throw new HTTPException21(409, {
10178
- message: JSON.stringify({
10179
- message: error.message,
10180
- currentQueueVersion: error.currentQueueVersion,
10181
- ...error.currentItemVersion !== void 0 ? { currentItemVersion: error.currentItemVersion } : {}
10182
- })
10183
- });
10328
+ function commandConflictResponse(c, error) {
10329
+ if (error instanceof QueueCommandConflictError) {
10330
+ return c.json({ code: error.code, message: error.message, current: error.current }, 409);
10331
+ }
10332
+ if (error instanceof SessionControlConflictError) {
10333
+ return c.json({ code: error.code, message: error.message }, 409);
10334
+ }
10335
+ if (error instanceof SessionCommandIdempotencyError) {
10336
+ return c.json({ code: error.code, message: error.message }, 409);
10184
10337
  }
10185
10338
  throw error;
10186
10339
  }
@@ -10329,6 +10482,7 @@ import {
10329
10482
  grantWorkspaceAccess,
10330
10483
  listScheduledTasks as listScheduledTasks3,
10331
10484
  listWorkspaceMembers,
10485
+ listWorkspaceControlEvents as listWorkspaceControlEvents2,
10332
10486
  listWorkspacesForSubject,
10333
10487
  removeWorkspaceMember,
10334
10488
  requireWorkspace,
@@ -10336,8 +10490,7 @@ import {
10336
10490
  setWorkspaceDefaultRig,
10337
10491
  updateWorkspace,
10338
10492
  updateWorkspaceSettings,
10339
- upsertWorkspaceModelPolicy,
10340
- setWorkspaceInferenceControl
10493
+ upsertWorkspaceModelPolicy
10341
10494
  } from "@opengeni/db";
10342
10495
  import { HTTPException as HTTPException23 } from "hono/http-exception";
10343
10496
  import { hasPermission as hasPermission3, requireAccessContext as requireAccessContext2, requireAccessGrant as requireAccessGrant16 } from "@opengeni/core";
@@ -10345,6 +10498,7 @@ import { requireLimit as requireLimit7 } from "@opengeni/core";
10345
10498
  import {
10346
10499
  assertWorkspaceDeletable,
10347
10500
  assertWorkspaceMemberRemovable,
10501
+ controlHumanWorkspace,
10348
10502
  resolveMemberSubjectId
10349
10503
  } from "@opengeni/core";
10350
10504
  function registerWorkspaceRoutes(app, deps) {
@@ -10448,51 +10602,33 @@ function registerWorkspaceRoutes(app, deps) {
10448
10602
  const workspaceId = c.req.param("workspaceId");
10449
10603
  const grant = await requireAccessGrant16(c, deps, workspaceId, "workspace:admin");
10450
10604
  const payload = WorkspaceInferenceControlRequest.parse(await c.req.json());
10451
- const result = await setWorkspaceInferenceControl(deps.db, {
10452
- accountId: grant.accountId,
10453
- workspaceId,
10454
- actor: grant.subjectId,
10455
- state: payload.state,
10456
- reason: payload.reason,
10457
- clientEventId: payload.clientEventId,
10458
- expectedState: payload.expectedState,
10459
- expectedGeneration: payload.expectedGeneration,
10460
- exceptSessionIds: payload.exceptSessionIds
10461
- });
10462
- for (const broadcast of result.broadcasts) {
10463
- await deps.bus.publish(workspaceId, broadcast.sessionId, broadcast.events);
10464
- }
10465
- for (const control of result.controls) {
10466
- await deps.workflowClient.signalSessionControl({
10467
- accountId: control.accountId,
10468
- workspaceId,
10469
- sessionId: control.sessionId,
10470
- eventId: control.eventId,
10471
- workflowId: control.workflowId,
10472
- workflowWakeRevision: control.workflowWakeRevision
10473
- });
10474
- }
10475
- for (const wake of result.wakeSessions) {
10476
- await deps.workflowClient.wakeSessionWorkflow({
10477
- accountId: wake.accountId,
10478
- workspaceId,
10479
- sessionId: wake.sessionId,
10480
- workflowId: wake.workflowId,
10481
- wakeRevision: wake.workflowWakeRevision
10482
- });
10483
- }
10484
10605
  return c.json(
10485
- {
10486
- operationId: result.operationId,
10487
- state: result.state,
10488
- generation: result.generation,
10489
- affectedSessionIds: result.affectedSessionIds,
10490
- controlSessionIds: result.controls.map((entry) => entry.sessionId),
10491
- exceptionSessionIds: result.exceptionSessionIds
10492
- },
10493
- 202
10606
+ await controlHumanWorkspace(
10607
+ { db: deps.db, bus: deps.bus, workflowClient: deps.workflowClient },
10608
+ { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
10609
+ payload
10610
+ )
10611
+ );
10612
+ });
10613
+ app.get("/v1/workspaces/:workspaceId/control-events", async (c) => {
10614
+ const workspaceId = c.req.param("workspaceId");
10615
+ await requireAccessGrant16(c, deps, workspaceId, "workspace:read");
10616
+ const after = Math.max(0, Number.parseInt(c.req.query("after") ?? "0", 10) || 0);
10617
+ return c.json(
10618
+ await listWorkspaceControlEvents2(
10619
+ deps.db,
10620
+ workspaceId,
10621
+ after,
10622
+ boundedLimit(c.req.query("limit"))
10623
+ )
10494
10624
  );
10495
10625
  });
10626
+ app.get("/v1/workspaces/:workspaceId/control-events/stream", async (c) => {
10627
+ const workspaceId = c.req.param("workspaceId");
10628
+ await requireAccessGrant16(c, deps, workspaceId, "workspace:read");
10629
+ const after = Math.max(0, Number.parseInt(c.req.query("after") ?? "0", 10) || 0);
10630
+ return await sseWorkspaceControlStream(deps.db, deps.bus, workspaceId, after, c.req.raw.signal);
10631
+ });
10496
10632
  app.put("/v1/workspaces/:workspaceId/default-rig", async (c) => {
10497
10633
  const workspaceId = c.req.param("workspaceId");
10498
10634
  await requireAccessGrant16(c, deps, workspaceId, "rigs:manage");
@@ -10610,7 +10746,8 @@ import {
10610
10746
  validateToolRefs,
10611
10747
  withDefaultEnabledCapabilityMcpTools
10612
10748
  } from "@opengeni/core";
10613
- import { workflowIdForSession as workflowIdForSession3 } from "@opengeni/core";
10749
+ import { workflowIdForSession as workflowIdForSession2 } from "@opengeni/core";
10750
+ var API_MAX_REQUEST_BODY_BYTES = 8 * 1024 * 1024;
10614
10751
  function createApp(deps) {
10615
10752
  const managedAuth = deps.managedAuth ?? createManagedAuth(deps.settings, deps.db);
10616
10753
  const objectStorage = deps.objectStorage === void 0 ? createObjectStorage(deps.settings) : deps.objectStorage;
@@ -10626,7 +10763,9 @@ function createApp(deps) {
10626
10763
  documentId
10627
10764
  }) => {
10628
10765
  if (!objectStorage) {
10629
- throw new HTTPException24(503, { message: "object storage is not configured" });
10766
+ throw new HTTPException24(503, {
10767
+ message: "object storage is not configured"
10768
+ });
10630
10769
  }
10631
10770
  return await indexDocumentNow(
10632
10771
  deps.db,
@@ -10665,6 +10804,15 @@ function createApp(deps) {
10665
10804
  "*",
10666
10805
  cors({
10667
10806
  credentials: true,
10807
+ allowHeaders: [
10808
+ "Accept",
10809
+ "Authorization",
10810
+ "Content-Type",
10811
+ "X-OpenGeni-Access-Key",
10812
+ "X-OpenGeni-Api-Contract",
10813
+ "X-OpenGeni-Subject"
10814
+ ],
10815
+ exposeHeaders: ["X-OpenGeni-Api-Contract"],
10668
10816
  origin: (origin) => {
10669
10817
  if (!origin) {
10670
10818
  return null;
@@ -10673,6 +10821,13 @@ function createApp(deps) {
10673
10821
  }
10674
10822
  })
10675
10823
  );
10824
+ app.use(
10825
+ "*",
10826
+ bodyLimit({
10827
+ maxSize: API_MAX_REQUEST_BODY_BYTES,
10828
+ onError: (c) => c.json({ code: "PAYLOAD_TOO_LARGE", message: "Request body is too large." }, 413)
10829
+ })
10830
+ );
10676
10831
  app.use("*", async (c, next) => {
10677
10832
  const url = new URL(c.req.url);
10678
10833
  const route = routeLabel(url.pathname);
@@ -10686,7 +10841,12 @@ function createApp(deps) {
10686
10841
  await next();
10687
10842
  const status = c.res.status || 200;
10688
10843
  const durationSeconds = (performance.now() - start) / 1e3;
10689
- observability.recordHttpRequest({ method: c.req.method, route, status, durationSeconds });
10844
+ observability.recordHttpRequest({
10845
+ method: c.req.method,
10846
+ route,
10847
+ status,
10848
+ durationSeconds
10849
+ });
10690
10850
  span.end({
10691
10851
  attributes: {
10692
10852
  "http.response.status_code": status,
@@ -10704,7 +10864,12 @@ function createApp(deps) {
10704
10864
  } catch (error) {
10705
10865
  const status = httpStatusForError(error);
10706
10866
  const durationSeconds = (performance.now() - start) / 1e3;
10707
- observability.recordHttpRequest({ method: c.req.method, route, status, durationSeconds });
10867
+ observability.recordHttpRequest({
10868
+ method: c.req.method,
10869
+ route,
10870
+ status,
10871
+ durationSeconds
10872
+ });
10708
10873
  span.end({
10709
10874
  attributes: {
10710
10875
  "http.response.status_code": status,
@@ -10725,6 +10890,20 @@ function createApp(deps) {
10725
10890
  }
10726
10891
  });
10727
10892
  app.use("*", requireAccessKey(deps.settings));
10893
+ app.use("/v1/*", async (c, next) => {
10894
+ c.header(OPENGENI_API_CONTRACT_HEADER, OPENGENI_API_CONTRACT_REVISION);
10895
+ if (deps.settings.environment !== "test" && isApiContractProtectedMutation(c.req.method, new URL(c.req.url).pathname) && c.req.header(OPENGENI_API_CONTRACT_HEADER) !== OPENGENI_API_CONTRACT_REVISION) {
10896
+ return c.json(
10897
+ {
10898
+ code: "API_CONTRACT_CHANGED",
10899
+ message: "OpenGeni updated. Reload this client before changing state.",
10900
+ apiContractRevision: OPENGENI_API_CONTRACT_REVISION
10901
+ },
10902
+ 409
10903
+ );
10904
+ }
10905
+ await next();
10906
+ });
10728
10907
  if (managedAuth) {
10729
10908
  app.on(["GET", "POST"], "/v1/auth/*", (c) => managedAuth.handler(c.req.raw));
10730
10909
  }
@@ -10748,11 +10927,12 @@ function createApp(deps) {
10748
10927
  "content-type": "text/plain; version=0.0.4; charset=utf-8"
10749
10928
  })
10750
10929
  );
10751
- app.get(
10752
- "/v1/config/client",
10753
- (c) => c.json(
10930
+ app.get("/v1/config/client", (c) => {
10931
+ c.header("cache-control", "no-store");
10932
+ return c.json(
10754
10933
  ClientConfig.parse({
10755
10934
  deploymentRevision: deps.settings.deploymentRevision,
10935
+ apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
10756
10936
  ...deps.settings.serverVersion ? { serverVersion: deps.settings.serverVersion } : {},
10757
10937
  defaultModel: deps.settings.openaiModel,
10758
10938
  allowedModels: configuredAllowedModels(deps.settings),
@@ -10785,15 +10965,17 @@ function createApp(deps) {
10785
10965
  // Per-session availability is still negotiated on /stream-capabilities.
10786
10966
  structuredServices: structuredServicesHint(deps.settings.sandboxBackend)
10787
10967
  })
10788
- )
10789
- );
10968
+ );
10969
+ });
10790
10970
  app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
10791
10971
  const workspaceId = c.req.param("workspaceId");
10792
10972
  const grant = await requireMcpAccessGrant(c, routeDeps, workspaceId);
10793
10973
  const toolspace = isToolspaceGrant(routeDeps.settings, grant) ? await prepareToolspaceMcpSurface({ deps: routeDeps, grant }) : null;
10794
10974
  const workspace = await getWorkspace2(routeDeps.db, workspaceId);
10795
10975
  const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
10796
- const transport = new WebStandardStreamableHTTPServerTransport2({ enableJsonResponse: true });
10976
+ const transport = new WebStandardStreamableHTTPServerTransport2({
10977
+ enableJsonResponse: true
10978
+ });
10797
10979
  const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
10798
10980
  requestOrigin: new URL(c.req.url).origin,
10799
10981
  toolspace,
@@ -10850,7 +11032,10 @@ function clientAuthConfig(settings) {
10850
11032
  };
10851
11033
  }
10852
11034
  if (settings.authRequired) {
10853
- return { mode: "deploymentKey", headerName: "x-opengeni-access-key" };
11035
+ return {
11036
+ mode: "deploymentKey",
11037
+ headerName: "x-opengeni-access-key"
11038
+ };
10854
11039
  }
10855
11040
  return { mode: "none" };
10856
11041
  }
@@ -10892,7 +11077,10 @@ async function runReadinessChecks(checks, timeoutMs) {
10892
11077
  } catch (error) {
10893
11078
  return [
10894
11079
  name,
10895
- { ok: false, error: error instanceof Error ? error.message : String(error) }
11080
+ {
11081
+ ok: false,
11082
+ error: error instanceof Error ? error.message : String(error)
11083
+ }
10896
11084
  ];
10897
11085
  }
10898
11086
  }
@@ -10941,15 +11129,24 @@ var routeLabelPatterns = [
10941
11129
  pattern: /^\/v1\/workspaces\/[^/]+\/codex\/usage$/,
10942
11130
  label: "/v1/workspaces/:workspaceId/codex/usage"
10943
11131
  },
10944
- { pattern: /^\/v1\/workspaces\/[^/]+\/codex$/, label: "/v1/workspaces/:workspaceId/codex" },
11132
+ {
11133
+ pattern: /^\/v1\/workspaces\/[^/]+\/codex$/,
11134
+ label: "/v1/workspaces/:workspaceId/codex"
11135
+ },
10945
11136
  { pattern: /^\/metrics$/, label: "/metrics" },
10946
11137
  { pattern: /^\/v1\/config\/client$/, label: "/v1/config/client" },
10947
11138
  { pattern: /^\/v1\/billing$/, label: "/v1/billing" },
10948
11139
  { pattern: /^\/v1\/billing\/checkout$/, label: "/v1/billing/checkout" },
10949
11140
  { pattern: /^\/v1\/billing\/usage$/, label: "/v1/billing/usage" },
10950
- { pattern: /^\/v1\/billing\/entitlements$/, label: "/v1/billing/entitlements" },
11141
+ {
11142
+ pattern: /^\/v1\/billing\/entitlements$/,
11143
+ label: "/v1/billing/entitlements"
11144
+ },
10951
11145
  { pattern: /^\/v1\/webhooks\/stripe$/, label: "/v1/webhooks/stripe" },
10952
- { pattern: /^\/v1\/workspaces\/[^/]+\/mcp$/, label: "/v1/workspaces/:workspaceId/mcp" },
11146
+ {
11147
+ pattern: /^\/v1\/workspaces\/[^/]+\/mcp$/,
11148
+ label: "/v1/workspaces/:workspaceId/mcp"
11149
+ },
10953
11150
  {
10954
11151
  pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/docs$/,
10955
11152
  label: "/v1/workspaces/:workspaceId/mcp/docs"
@@ -10958,7 +11155,22 @@ var routeLabelPatterns = [
10958
11155
  pattern: /^\/v1\/workspaces\/[^/]+\/default-rig$/,
10959
11156
  label: "/v1/workspaces/:workspaceId/default-rig"
10960
11157
  },
10961
- { pattern: /^\/v1\/workspaces\/[^/]+\/sessions$/, label: "/v1/workspaces/:workspaceId/sessions" },
11158
+ {
11159
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions$/,
11160
+ label: "/v1/workspaces/:workspaceId/sessions"
11161
+ },
11162
+ {
11163
+ pattern: /^\/v1\/workspaces\/[^/]+\/control-events\/stream$/,
11164
+ label: "/v1/workspaces/:workspaceId/control-events/stream"
11165
+ },
11166
+ {
11167
+ pattern: /^\/v1\/workspaces\/[^/]+\/control-events$/,
11168
+ label: "/v1/workspaces/:workspaceId/control-events"
11169
+ },
11170
+ {
11171
+ pattern: /^\/v1\/workspaces\/[^/]+\/inference-control$/,
11172
+ label: "/v1/workspaces/:workspaceId/inference-control"
11173
+ },
10962
11174
  {
10963
11175
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/events\/stream$/,
10964
11176
  label: "/v1/workspaces/:workspaceId/sessions/:id/events/stream"
@@ -10972,16 +11184,20 @@ var routeLabelPatterns = [
10972
11184
  label: "/v1/workspaces/:workspaceId/sessions/:id/events"
10973
11185
  },
10974
11186
  {
10975
- pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns\/reorder$/,
10976
- label: "/v1/workspaces/:workspaceId/sessions/:id/turns/reorder"
11187
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue\/[^/]+\/(move|edit|steer|delete)$/,
11188
+ label: "/v1/workspaces/:workspaceId/sessions/:id/queue/:turnId/:action"
11189
+ },
11190
+ {
11191
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/queue$/,
11192
+ label: "/v1/workspaces/:workspaceId/sessions/:id/queue"
10977
11193
  },
10978
11194
  {
10979
- pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns\/[^/]+$/,
10980
- label: "/v1/workspaces/:workspaceId/sessions/:id/turns/:turnId"
11195
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/composer-draft$/,
11196
+ label: "/v1/workspaces/:workspaceId/sessions/:id/composer-draft"
10981
11197
  },
10982
11198
  {
10983
- pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/turns$/,
10984
- label: "/v1/workspaces/:workspaceId/sessions/:id/turns"
11199
+ pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/(control|steer)$/,
11200
+ label: "/v1/workspaces/:workspaceId/sessions/:id/:controlAction"
10985
11201
  },
10986
11202
  {
10987
11203
  pattern: /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/stream-capabilities$/,
@@ -11023,7 +11239,10 @@ var routeLabelPatterns = [
11023
11239
  pattern: /^\/v1\/workspaces\/[^/]+\/files\/[^/]+$/,
11024
11240
  label: "/v1/workspaces/:workspaceId/files/:id"
11025
11241
  },
11026
- { pattern: /^\/v1\/workspaces\/[^/]+\/api-keys$/, label: "/v1/workspaces/:workspaceId/api-keys" },
11242
+ {
11243
+ pattern: /^\/v1\/workspaces\/[^/]+\/api-keys$/,
11244
+ label: "/v1/workspaces/:workspaceId/api-keys"
11245
+ },
11027
11246
  {
11028
11247
  pattern: /^\/v1\/workspaces\/[^/]+\/api-keys\/[^/]+$/,
11029
11248
  label: "/v1/workspaces/:workspaceId/api-keys/:id"
@@ -11132,7 +11351,10 @@ var routeLabelPatterns = [
11132
11351
  pattern: /^\/v1\/workspaces\/[^/]+\/environments\/[^/]+$/,
11133
11352
  label: "/v1/workspaces/:workspaceId/environments/:id"
11134
11353
  },
11135
- { pattern: /^\/v1\/workspaces\/[^/]+\/packs$/, label: "/v1/workspaces/:workspaceId/packs" },
11354
+ {
11355
+ pattern: /^\/v1\/workspaces\/[^/]+\/packs$/,
11356
+ label: "/v1/workspaces/:workspaceId/packs"
11357
+ },
11136
11358
  {
11137
11359
  pattern: /^\/v1\/workspaces\/[^/]+\/packs\/installations$/,
11138
11360
  label: "/v1/workspaces/:workspaceId/packs/installations"
@@ -11170,13 +11392,22 @@ var routeLabelPatterns = [
11170
11392
  label: "/v1/workspaces/:workspaceId/connections/:connectionId"
11171
11393
  },
11172
11394
  { pattern: /^\/v1\/catalog-assets\/.+$/, label: "/v1/catalog-assets/*" },
11173
- { pattern: /^\/v1\/integrations\/oauth\/callback$/, label: "/v1/integrations/oauth/callback" },
11395
+ {
11396
+ pattern: /^\/v1\/integrations\/oauth\/callback$/,
11397
+ label: "/v1/integrations/oauth/callback"
11398
+ },
11174
11399
  {
11175
11400
  pattern: /^\/v1\/integrations\/oauth\/client-metadata\.json$/,
11176
11401
  label: "/v1/integrations/oauth/client-metadata.json"
11177
11402
  },
11178
- { pattern: /^\/v1\/enrollments\/device\/start$/, label: "/v1/enrollments/device/start" },
11179
- { pattern: /^\/v1\/enrollments\/device\/poll$/, label: "/v1/enrollments/device/poll" },
11403
+ {
11404
+ pattern: /^\/v1\/enrollments\/device\/start$/,
11405
+ label: "/v1/enrollments/device/start"
11406
+ },
11407
+ {
11408
+ pattern: /^\/v1\/enrollments\/device\/poll$/,
11409
+ label: "/v1/enrollments/device/poll"
11410
+ },
11180
11411
  {
11181
11412
  pattern: /^\/v1\/workspaces\/[^/]+\/enrollments\/device\/approve$/,
11182
11413
  label: "/v1/workspaces/:workspaceId/enrollments/device/approve"
@@ -11193,11 +11424,23 @@ var routeLabelPatterns = [
11193
11424
  pattern: /^\/v1\/workspaces\/[^/]+\/machines\/[^/]+\/metrics\/series$/,
11194
11425
  label: "/v1/workspaces/:workspaceId/machines/:enrollmentId/metrics/series"
11195
11426
  },
11196
- { pattern: /^\/v1\/workspaces\/[^/]+\/machines$/, label: "/v1/workspaces/:workspaceId/machines" },
11197
- { pattern: /^\/v1\/github\/app-manifest\/callback$/, label: "/v1/github/app-manifest/callback" },
11427
+ {
11428
+ pattern: /^\/v1\/workspaces\/[^/]+\/machines$/,
11429
+ label: "/v1/workspaces/:workspaceId/machines"
11430
+ },
11431
+ {
11432
+ pattern: /^\/v1\/github\/app-manifest\/callback$/,
11433
+ label: "/v1/github/app-manifest/callback"
11434
+ },
11198
11435
  { pattern: /^\/v1\/github\/setup$/, label: "/v1/github/setup" },
11199
- { pattern: /^\/v1\/github\/install\/callback$/, label: "/v1/github/install/callback" },
11200
- { pattern: /^\/v1\/github\/oauth\/callback$/, label: "/v1/github/oauth/callback" }
11436
+ {
11437
+ pattern: /^\/v1\/github\/install\/callback$/,
11438
+ label: "/v1/github/install/callback"
11439
+ },
11440
+ {
11441
+ pattern: /^\/v1\/github\/oauth\/callback$/,
11442
+ label: "/v1/github/oauth/callback"
11443
+ }
11201
11444
  ];
11202
11445
  function routeLabel(pathname) {
11203
11446
  const match = routeLabelPatterns.find(({ pattern }) => pattern.test(pathname));
@@ -11206,14 +11449,29 @@ function routeLabel(pathname) {
11206
11449
  }
11207
11450
  return pathname.startsWith("/v1/") ? "/v1/unknown" : "/unknown";
11208
11451
  }
11452
+ function isApiContractProtectedMutation(method, pathname) {
11453
+ if (!(/* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"])).has(method.toUpperCase())) {
11454
+ return false;
11455
+ }
11456
+ if (!pathname.startsWith("/v1/")) {
11457
+ return false;
11458
+ }
11459
+ if (pathname.startsWith("/v1/auth/") || pathname.startsWith("/v1/webhooks/") || pathname.startsWith("/v1/integrations/oauth/") || pathname.startsWith("/v1/github/") || pathname === "/v1/enrollments/device/start" || pathname === "/v1/enrollments/device/poll" || pathname === "/v1/enrollments/token/exchange") {
11460
+ return false;
11461
+ }
11462
+ return !pathname.split("/").includes("mcp");
11463
+ }
11209
11464
 
11210
11465
  export {
11211
11466
  sseSessionStream,
11212
11467
  replaySessionEvents,
11468
+ sseWorkspaceControlStream,
11469
+ API_MAX_REQUEST_BODY_BYTES,
11213
11470
  createApp,
11214
11471
  allowedCorsOrigin,
11215
11472
  httpStatusForError,
11216
11473
  routeLabel,
11474
+ isApiContractProtectedMutation,
11217
11475
  mergeResourceRefs,
11218
11476
  mergeToolRefs,
11219
11477
  normalizeResources,
@@ -11222,6 +11480,6 @@ export {
11222
11480
  validateGitHubRepositorySelectionShape,
11223
11481
  validateToolRefs,
11224
11482
  withDefaultEnabledCapabilityMcpTools,
11225
- workflowIdForSession3 as workflowIdForSession
11483
+ workflowIdForSession2 as workflowIdForSession
11226
11484
  };
11227
- //# sourceMappingURL=chunk-DO2G3JSB.js.map
11485
+ //# sourceMappingURL=chunk-HBEJMWD3.js.map