@opengeni/api-router 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +9 -1
- package/dist/app.js +7 -1
- package/dist/{chunk-DO2G3JSB.js → chunk-HBEJMWD3.js} +528 -270
- package/dist/chunk-HBEJMWD3.js.map +1 -0
- package/dist/index.js +8 -27
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/app.ts +180 -31
- package/src/http/sse.ts +57 -2
- package/src/index.ts +8 -27
- package/src/mcp/server.ts +167 -85
- package/src/routes/sessions.ts +141 -109
- package/src/routes/workspaces.ts +30 -43
- package/dist/chunk-DO2G3JSB.js.map +0 -1
package/src/routes/sessions.ts
CHANGED
|
@@ -2,9 +2,10 @@ import {
|
|
|
2
2
|
AcknowledgeStreamRequest,
|
|
3
3
|
AttachViewerRequest,
|
|
4
4
|
ClearSessionContextRequest,
|
|
5
|
-
CancelSessionQueueItemRequest,
|
|
6
5
|
ClientSessionEvent,
|
|
7
6
|
CompactSessionContextRequest,
|
|
7
|
+
DeleteSessionQueueItemRequest,
|
|
8
|
+
EditSessionQueueItemRequest,
|
|
8
9
|
FsDeleteRequest,
|
|
9
10
|
FsListRequest,
|
|
10
11
|
FsMkdirRequest,
|
|
@@ -15,11 +16,14 @@ import {
|
|
|
15
16
|
GitLogRequest,
|
|
16
17
|
GitShowRequest,
|
|
17
18
|
GitStatusRequest,
|
|
19
|
+
MoveSessionQueueItemRequest,
|
|
18
20
|
PtyCloseRequest,
|
|
19
21
|
PtyOpenRequest,
|
|
20
22
|
PtyResizeRequest,
|
|
21
23
|
PtyWriteRequest,
|
|
22
24
|
SessionControlRequest,
|
|
25
|
+
SaveComposerDraftRequest,
|
|
26
|
+
SteerSessionQueueItemRequest,
|
|
23
27
|
SteerSessionMessageRequest,
|
|
24
28
|
TerminalExecRequest,
|
|
25
29
|
UpdateSessionPinRequest,
|
|
@@ -34,7 +38,6 @@ import {
|
|
|
34
38
|
} from "@opengeni/contracts";
|
|
35
39
|
import { streamTokenDegraded } from "@opengeni/config";
|
|
36
40
|
import {
|
|
37
|
-
cancelQueuedSessionTurnWithVersion,
|
|
38
41
|
acceptSessionApprovalDecision,
|
|
39
42
|
clearSessionGoal,
|
|
40
43
|
clearSessionContext,
|
|
@@ -64,8 +67,9 @@ import {
|
|
|
64
67
|
revokeViewer,
|
|
65
68
|
setSessionGoalStatus,
|
|
66
69
|
updatePtySessionActivity,
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
QueueCommandConflictError,
|
|
71
|
+
SessionCommandIdempotencyError,
|
|
72
|
+
SessionControlConflictError,
|
|
69
73
|
SessionContextBusyError,
|
|
70
74
|
latestWorkspaceCapture,
|
|
71
75
|
workspaceCaptureAtRevision,
|
|
@@ -97,8 +101,15 @@ import {
|
|
|
97
101
|
} from "../sandbox/viewer";
|
|
98
102
|
import {
|
|
99
103
|
acceptSessionUserMessage,
|
|
104
|
+
controlHumanSessionWorkstream,
|
|
100
105
|
createSessionForRequest,
|
|
106
|
+
deleteHumanQueuePrompt,
|
|
107
|
+
editHumanQueuePrompt,
|
|
108
|
+
getHumanComposerDraft,
|
|
109
|
+
moveHumanQueuePrompt,
|
|
101
110
|
readSessionLineage,
|
|
111
|
+
saveHumanComposerDraft,
|
|
112
|
+
steerHumanQueuePrompt,
|
|
102
113
|
updateSessionTitle,
|
|
103
114
|
workflowIdForSession,
|
|
104
115
|
} from "@opengeni/core";
|
|
@@ -508,115 +519,136 @@ export function registerSessionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
508
519
|
return c.json(snapshot);
|
|
509
520
|
});
|
|
510
521
|
|
|
511
|
-
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/
|
|
522
|
+
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/move", async (c) => {
|
|
512
523
|
const workspaceId = c.req.param("workspaceId");
|
|
513
524
|
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
|
|
514
525
|
const sessionId = c.req.param("sessionId");
|
|
515
526
|
await assertSessionExists(db, workspaceId, sessionId);
|
|
516
|
-
const payload =
|
|
527
|
+
const payload = MoveSessionQueueItemRequest.parse(await c.req.json());
|
|
517
528
|
try {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
grant.subjectId,
|
|
526
|
-
payload.reason ?? null,
|
|
529
|
+
return c.json(
|
|
530
|
+
await moveHumanQueuePrompt(
|
|
531
|
+
{ db, bus },
|
|
532
|
+
{ accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
|
|
533
|
+
c.req.param("turnId"),
|
|
534
|
+
payload,
|
|
535
|
+
),
|
|
527
536
|
);
|
|
528
|
-
await bus.publish(workspaceId, sessionId, result.events);
|
|
529
|
-
if (result.shouldWake) {
|
|
530
|
-
if (result.workflowWakeRevision === null) {
|
|
531
|
-
throw new Error("Queue continuation has no workflow wake revision");
|
|
532
|
-
}
|
|
533
|
-
await workflowClient.wakeSessionWorkflow({
|
|
534
|
-
accountId: grant.accountId,
|
|
535
|
-
workspaceId,
|
|
536
|
-
sessionId,
|
|
537
|
-
workflowId: workflowIdForSession(sessionId),
|
|
538
|
-
wakeRevision: result.workflowWakeRevision,
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
return c.json(result);
|
|
542
537
|
} catch (error) {
|
|
543
|
-
|
|
538
|
+
return commandConflictResponse(c, error);
|
|
544
539
|
}
|
|
545
540
|
});
|
|
546
541
|
|
|
547
|
-
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/
|
|
542
|
+
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/edit", async (c) => {
|
|
548
543
|
const workspaceId = c.req.param("workspaceId");
|
|
549
544
|
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
|
|
550
545
|
const sessionId = c.req.param("sessionId");
|
|
551
|
-
|
|
552
|
-
|
|
546
|
+
await assertSessionExists(db, workspaceId, sessionId);
|
|
547
|
+
const payload = EditSessionQueueItemRequest.parse(await c.req.json());
|
|
553
548
|
try {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
...(payload.expectedControlState !== undefined
|
|
563
|
-
? { expectedControlState: payload.expectedControlState }
|
|
564
|
-
: {}),
|
|
565
|
-
...(payload.expectedControlGeneration !== undefined
|
|
566
|
-
? { expectedControlGeneration: payload.expectedControlGeneration }
|
|
567
|
-
: {}),
|
|
568
|
-
...(payload.expectedWorkspaceInferenceGeneration !== undefined
|
|
569
|
-
? {
|
|
570
|
-
expectedWorkspaceInferenceGeneration: payload.expectedWorkspaceInferenceGeneration,
|
|
571
|
-
}
|
|
572
|
-
: {}),
|
|
573
|
-
});
|
|
549
|
+
return c.json(
|
|
550
|
+
await editHumanQueuePrompt(
|
|
551
|
+
{ db, bus },
|
|
552
|
+
{ accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
|
|
553
|
+
c.req.param("turnId"),
|
|
554
|
+
payload,
|
|
555
|
+
),
|
|
556
|
+
);
|
|
574
557
|
} catch (error) {
|
|
575
|
-
|
|
558
|
+
return commandConflictResponse(c, error);
|
|
576
559
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/steer", async (c) => {
|
|
563
|
+
const workspaceId = c.req.param("workspaceId");
|
|
564
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
|
|
565
|
+
const sessionId = c.req.param("sessionId");
|
|
566
|
+
await assertSessionExists(db, workspaceId, sessionId);
|
|
567
|
+
const payload = SteerSessionQueueItemRequest.parse(await c.req.json());
|
|
568
|
+
try {
|
|
569
|
+
return c.json(
|
|
570
|
+
await steerHumanQueuePrompt(
|
|
571
|
+
{ db, bus },
|
|
572
|
+
{ accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
|
|
573
|
+
c.req.param("turnId"),
|
|
574
|
+
payload,
|
|
575
|
+
),
|
|
576
|
+
);
|
|
577
|
+
} catch (error) {
|
|
578
|
+
return commandConflictResponse(c, error);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/queue/:turnId/delete", async (c) => {
|
|
583
|
+
const workspaceId = c.req.param("workspaceId");
|
|
584
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
|
|
585
|
+
const sessionId = c.req.param("sessionId");
|
|
586
|
+
await assertSessionExists(db, workspaceId, sessionId);
|
|
587
|
+
const payload = DeleteSessionQueueItemRequest.parse(await c.req.json());
|
|
588
|
+
try {
|
|
589
|
+
return c.json(
|
|
590
|
+
await deleteHumanQueuePrompt(
|
|
591
|
+
{ db, bus },
|
|
592
|
+
{ accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
|
|
593
|
+
c.req.param("turnId"),
|
|
594
|
+
payload,
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
return commandConflictResponse(c, error);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/composer-draft", async (c) => {
|
|
603
|
+
const workspaceId = c.req.param("workspaceId");
|
|
604
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:read");
|
|
605
|
+
const sessionId = c.req.param("sessionId");
|
|
606
|
+
return c.json(
|
|
607
|
+
await getHumanComposerDraft(db, {
|
|
596
608
|
accountId: grant.accountId,
|
|
597
609
|
workspaceId,
|
|
598
610
|
sessionId,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
return c.json(
|
|
604
|
-
{
|
|
605
|
-
operationId: result.operationId,
|
|
606
|
-
event: result.event,
|
|
607
|
-
controlState: result.controlState,
|
|
608
|
-
controlGeneration: result.controlGeneration,
|
|
609
|
-
expectedActiveTurnId: result.expectedActiveTurnId,
|
|
610
|
-
expectedExecutionGeneration: result.expectedExecutionGeneration,
|
|
611
|
-
expectedAttemptId: result.expectedAttemptId,
|
|
612
|
-
deliveryEventId: result.deliveryEventId,
|
|
613
|
-
shouldSignalControl: result.shouldSignalControl,
|
|
614
|
-
shouldWake: result.shouldWake,
|
|
615
|
-
},
|
|
616
|
-
202,
|
|
611
|
+
subjectId: grant.subjectId,
|
|
612
|
+
}),
|
|
617
613
|
);
|
|
618
614
|
});
|
|
619
615
|
|
|
616
|
+
app.put("/v1/workspaces/:workspaceId/sessions/:sessionId/composer-draft", async (c) => {
|
|
617
|
+
const workspaceId = c.req.param("workspaceId");
|
|
618
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
|
|
619
|
+
const sessionId = c.req.param("sessionId");
|
|
620
|
+
const payload = SaveComposerDraftRequest.parse(await c.req.json());
|
|
621
|
+
try {
|
|
622
|
+
return c.json(
|
|
623
|
+
await saveHumanComposerDraft(
|
|
624
|
+
db,
|
|
625
|
+
{ accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
|
|
626
|
+
payload,
|
|
627
|
+
),
|
|
628
|
+
);
|
|
629
|
+
} catch (error) {
|
|
630
|
+
return commandConflictResponse(c, error);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/control", async (c) => {
|
|
635
|
+
const workspaceId = c.req.param("workspaceId");
|
|
636
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
|
|
637
|
+
const sessionId = c.req.param("sessionId");
|
|
638
|
+
const payload = SessionControlRequest.parse(await c.req.json());
|
|
639
|
+
try {
|
|
640
|
+
return c.json(
|
|
641
|
+
await controlHumanSessionWorkstream(
|
|
642
|
+
{ db, bus, workflowClient },
|
|
643
|
+
{ accountId: grant.accountId, workspaceId, sessionId, subjectId: grant.subjectId },
|
|
644
|
+
payload,
|
|
645
|
+
),
|
|
646
|
+
);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
return commandConflictResponse(c, error);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
620
652
|
app.post("/v1/workspaces/:workspaceId/sessions/:sessionId/steer", async (c) => {
|
|
621
653
|
const workspaceId = c.req.param("workspaceId");
|
|
622
654
|
const grant = await requireAccessGrant(c, deps, workspaceId, "sessions:control");
|
|
@@ -634,13 +666,9 @@ export function registerSessionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
634
666
|
mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
|
|
635
667
|
delivery: "steer",
|
|
636
668
|
origin: "human",
|
|
637
|
-
...(payload.
|
|
638
|
-
|
|
639
|
-
:
|
|
640
|
-
...(payload.expectedWorkspaceInferenceGeneration !== undefined
|
|
641
|
-
? {
|
|
642
|
-
expectedWorkspaceInferenceGeneration: payload.expectedWorkspaceInferenceGeneration,
|
|
643
|
-
}
|
|
669
|
+
...(payload.controlEtag !== undefined ? { controlEtag: payload.controlEtag } : {}),
|
|
670
|
+
...(payload.expectedDraftRevision !== undefined
|
|
671
|
+
? { expectedDraftRevision: payload.expectedDraftRevision }
|
|
644
672
|
: {}),
|
|
645
673
|
...(payload.clientEventId ? { clientEventId: payload.clientEventId } : {}),
|
|
646
674
|
});
|
|
@@ -662,6 +690,12 @@ export function registerSessionRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
662
690
|
model: event.payload.model ?? null,
|
|
663
691
|
reasoningEffort: event.payload.reasoningEffort ?? null,
|
|
664
692
|
mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
|
|
693
|
+
...(event.payload.controlEtag !== undefined
|
|
694
|
+
? { controlEtag: event.payload.controlEtag }
|
|
695
|
+
: {}),
|
|
696
|
+
...(event.payload.expectedDraftRevision !== undefined
|
|
697
|
+
? { expectedDraftRevision: event.payload.expectedDraftRevision }
|
|
698
|
+
: {}),
|
|
665
699
|
...(event.clientEventId ? { clientEventId: event.clientEventId } : {}),
|
|
666
700
|
});
|
|
667
701
|
return c.json(accepted, 202);
|
|
@@ -1629,17 +1663,15 @@ function userMessagePayloadHasOwnProperty(value: unknown, key: string): boolean
|
|
|
1629
1663
|
return hasOwnProperty(payload, key);
|
|
1630
1664
|
}
|
|
1631
1665
|
|
|
1632
|
-
function
|
|
1633
|
-
if (error instanceof
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
}),
|
|
1642
|
-
});
|
|
1666
|
+
function commandConflictResponse(c: Context, error: unknown): Response {
|
|
1667
|
+
if (error instanceof QueueCommandConflictError) {
|
|
1668
|
+
return c.json({ code: error.code, message: error.message, current: error.current }, 409);
|
|
1669
|
+
}
|
|
1670
|
+
if (error instanceof SessionControlConflictError) {
|
|
1671
|
+
return c.json({ code: error.code, message: error.message }, 409);
|
|
1672
|
+
}
|
|
1673
|
+
if (error instanceof SessionCommandIdempotencyError) {
|
|
1674
|
+
return c.json({ code: error.code, message: error.message }, 409);
|
|
1643
1675
|
}
|
|
1644
1676
|
throw error;
|
|
1645
1677
|
}
|
package/src/routes/workspaces.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
grantWorkspaceAccess,
|
|
25
25
|
listScheduledTasks,
|
|
26
26
|
listWorkspaceMembers,
|
|
27
|
+
listWorkspaceControlEvents,
|
|
27
28
|
listWorkspacesForSubject,
|
|
28
29
|
removeWorkspaceMember,
|
|
29
30
|
requireWorkspace,
|
|
@@ -32,7 +33,6 @@ import {
|
|
|
32
33
|
updateWorkspace,
|
|
33
34
|
updateWorkspaceSettings,
|
|
34
35
|
upsertWorkspaceModelPolicy,
|
|
35
|
-
setWorkspaceInferenceControl,
|
|
36
36
|
} from "@opengeni/db";
|
|
37
37
|
import type { Hono } from "hono";
|
|
38
38
|
import { HTTPException } from "hono/http-exception";
|
|
@@ -42,8 +42,11 @@ import type { ApiRouteDeps } from "@opengeni/core";
|
|
|
42
42
|
import {
|
|
43
43
|
assertWorkspaceDeletable,
|
|
44
44
|
assertWorkspaceMemberRemovable,
|
|
45
|
+
controlHumanWorkspace,
|
|
45
46
|
resolveMemberSubjectId,
|
|
46
47
|
} from "@opengeni/core";
|
|
48
|
+
import { boundedLimit } from "../http/common";
|
|
49
|
+
import { sseWorkspaceControlStream } from "../http/sse";
|
|
47
50
|
|
|
48
51
|
export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
49
52
|
app.get("/v1/access/me", async (c) => {
|
|
@@ -169,52 +172,36 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
169
172
|
const workspaceId = c.req.param("workspaceId");
|
|
170
173
|
const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
|
|
171
174
|
const payload = WorkspaceInferenceControlRequest.parse(await c.req.json());
|
|
172
|
-
const result = await setWorkspaceInferenceControl(deps.db, {
|
|
173
|
-
accountId: grant.accountId,
|
|
174
|
-
workspaceId,
|
|
175
|
-
actor: grant.subjectId,
|
|
176
|
-
state: payload.state,
|
|
177
|
-
reason: payload.reason,
|
|
178
|
-
clientEventId: payload.clientEventId,
|
|
179
|
-
expectedState: payload.expectedState,
|
|
180
|
-
expectedGeneration: payload.expectedGeneration,
|
|
181
|
-
exceptSessionIds: payload.exceptSessionIds,
|
|
182
|
-
});
|
|
183
|
-
for (const broadcast of result.broadcasts) {
|
|
184
|
-
await deps.bus.publish(workspaceId, broadcast.sessionId, broadcast.events);
|
|
185
|
-
}
|
|
186
|
-
for (const control of result.controls) {
|
|
187
|
-
await deps.workflowClient.signalSessionControl({
|
|
188
|
-
accountId: control.accountId,
|
|
189
|
-
workspaceId,
|
|
190
|
-
sessionId: control.sessionId,
|
|
191
|
-
eventId: control.eventId,
|
|
192
|
-
workflowId: control.workflowId,
|
|
193
|
-
workflowWakeRevision: control.workflowWakeRevision,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
for (const wake of result.wakeSessions) {
|
|
197
|
-
await deps.workflowClient.wakeSessionWorkflow({
|
|
198
|
-
accountId: wake.accountId,
|
|
199
|
-
workspaceId,
|
|
200
|
-
sessionId: wake.sessionId,
|
|
201
|
-
workflowId: wake.workflowId,
|
|
202
|
-
wakeRevision: wake.workflowWakeRevision,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
175
|
return c.json(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
controlSessionIds: result.controls.map((entry) => entry.sessionId),
|
|
212
|
-
exceptionSessionIds: result.exceptionSessionIds,
|
|
213
|
-
},
|
|
214
|
-
202,
|
|
176
|
+
await controlHumanWorkspace(
|
|
177
|
+
{ db: deps.db, bus: deps.bus, workflowClient: deps.workflowClient },
|
|
178
|
+
{ accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
|
|
179
|
+
payload,
|
|
180
|
+
),
|
|
215
181
|
);
|
|
216
182
|
});
|
|
217
183
|
|
|
184
|
+
app.get("/v1/workspaces/:workspaceId/control-events", async (c) => {
|
|
185
|
+
const workspaceId = c.req.param("workspaceId");
|
|
186
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
187
|
+
const after = Math.max(0, Number.parseInt(c.req.query("after") ?? "0", 10) || 0);
|
|
188
|
+
return c.json(
|
|
189
|
+
await listWorkspaceControlEvents(
|
|
190
|
+
deps.db,
|
|
191
|
+
workspaceId,
|
|
192
|
+
after,
|
|
193
|
+
boundedLimit(c.req.query("limit")),
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
app.get("/v1/workspaces/:workspaceId/control-events/stream", async (c) => {
|
|
199
|
+
const workspaceId = c.req.param("workspaceId");
|
|
200
|
+
await requireAccessGrant(c, deps, workspaceId, "workspace:read");
|
|
201
|
+
const after = Math.max(0, Number.parseInt(c.req.query("after") ?? "0", 10) || 0);
|
|
202
|
+
return await sseWorkspaceControlStream(deps.db, deps.bus, workspaceId, after, c.req.raw.signal);
|
|
203
|
+
});
|
|
204
|
+
|
|
218
205
|
app.put("/v1/workspaces/:workspaceId/default-rig", async (c) => {
|
|
219
206
|
const workspaceId = c.req.param("workspaceId");
|
|
220
207
|
await requireAccessGrant(c, deps, workspaceId, "rigs:manage");
|