@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/mcp/server.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
UpdateScheduledTaskRequest,
|
|
10
10
|
} from "@opengeni/contracts";
|
|
11
11
|
import {
|
|
12
|
-
addSessionSystemUpdate,
|
|
13
12
|
correctWorkspaceMemory,
|
|
14
13
|
countVariableSets,
|
|
15
14
|
beginRigChangeVerificationAttempt,
|
|
@@ -18,6 +17,7 @@ import {
|
|
|
18
17
|
encryptVariableSetValue,
|
|
19
18
|
getSession,
|
|
20
19
|
getSessionGoal,
|
|
20
|
+
getSessionQueueSnapshot,
|
|
21
21
|
getSessionTurn,
|
|
22
22
|
getVariableSet,
|
|
23
23
|
getVariableSetByName,
|
|
@@ -36,10 +36,8 @@ import {
|
|
|
36
36
|
requireFile,
|
|
37
37
|
requireScheduledTask,
|
|
38
38
|
requireSession,
|
|
39
|
-
requestSessionControl,
|
|
40
39
|
saveWorkspaceMemory,
|
|
41
40
|
searchWorkspaceMemories,
|
|
42
|
-
setSessionChildNotificationsMode,
|
|
43
41
|
setSessionGoalStatus,
|
|
44
42
|
setVariableSetVariable,
|
|
45
43
|
updateScheduledTask,
|
|
@@ -87,9 +85,13 @@ import {
|
|
|
87
85
|
} from "@opengeni/core";
|
|
88
86
|
import {
|
|
89
87
|
acceptSessionUserMessage,
|
|
88
|
+
controlAgentSessionWorkstream,
|
|
89
|
+
controlHumanSessionWorkstream,
|
|
90
90
|
createSessionForRequest,
|
|
91
|
+
sendAgentSessionMessage,
|
|
92
|
+
steerAgentSession,
|
|
91
93
|
updateSessionTitle,
|
|
92
|
-
|
|
94
|
+
type AgentSessionCommandContext,
|
|
93
95
|
} from "@opengeni/core";
|
|
94
96
|
import {
|
|
95
97
|
buildFleetContextForSession,
|
|
@@ -151,25 +153,6 @@ export function buildOpenGeniMcpServer(
|
|
|
151
153
|
},
|
|
152
154
|
);
|
|
153
155
|
}
|
|
154
|
-
if (sessionId !== null && !toolspaceMode && can("sessions:create")) {
|
|
155
|
-
server.registerTool(
|
|
156
|
-
"set_child_notifications_mode",
|
|
157
|
-
{
|
|
158
|
-
description:
|
|
159
|
-
"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.",
|
|
160
|
-
inputSchema: { mode: z4.enum(["digest", "passive"]) },
|
|
161
|
-
},
|
|
162
|
-
async ({ mode }) => {
|
|
163
|
-
const changed = await setSessionChildNotificationsMode(
|
|
164
|
-
deps.db,
|
|
165
|
-
grant.workspaceId,
|
|
166
|
-
sessionId,
|
|
167
|
-
mode,
|
|
168
|
-
);
|
|
169
|
-
return json({ ok: true, changed, mode });
|
|
170
|
-
},
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
156
|
// Goal tools require goals:manage (in the default first-party permission set).
|
|
174
157
|
if (sessionId !== null && can("goals:manage")) {
|
|
175
158
|
registerGoalTools(server, deps, grant, sessionId, json);
|
|
@@ -204,7 +187,7 @@ export function buildOpenGeniMcpServer(
|
|
|
204
187
|
// and mint GitHub install links out of the box. A user DEMOTES a specific
|
|
205
188
|
// session by setting a narrower session.firstPartyMcpPermissions (capped to
|
|
206
189
|
// the creator's own grant); operators still cap what any session can be given.
|
|
207
|
-
registerWorkspaceOrchestrationTools(server, deps, grant, can, sessionId, json);
|
|
190
|
+
registerWorkspaceOrchestrationTools(server, deps, grant, can, sessionId, toolspaceMode, json);
|
|
208
191
|
registerVariableSetTools(server, deps, grant, can, json);
|
|
209
192
|
if (can("github:use")) {
|
|
210
193
|
registerGitHubConnectTool(server, deps, grant, options, json);
|
|
@@ -1235,12 +1218,39 @@ function registerRigTools(
|
|
|
1235
1218
|
// Workspace orchestration for manager-style agents. Session-authenticated
|
|
1236
1219
|
// workers communicate through the typed internal-update plane; only a
|
|
1237
1220
|
// sessionless operator can append a visible prompt through this surface.
|
|
1221
|
+
function exactAgentCommandContext(
|
|
1222
|
+
grant: AccessGrant,
|
|
1223
|
+
callerSessionId: string,
|
|
1224
|
+
): AgentSessionCommandContext {
|
|
1225
|
+
const turnId = grant.metadata?.["turnId"];
|
|
1226
|
+
const attemptId = grant.metadata?.["attemptId"];
|
|
1227
|
+
const executionGeneration = grant.metadata?.["executionGeneration"];
|
|
1228
|
+
if (
|
|
1229
|
+
typeof turnId !== "string" ||
|
|
1230
|
+
typeof attemptId !== "string" ||
|
|
1231
|
+
typeof executionGeneration !== "number" ||
|
|
1232
|
+
!Number.isSafeInteger(executionGeneration) ||
|
|
1233
|
+
executionGeneration < 1
|
|
1234
|
+
) {
|
|
1235
|
+
throw new Error("caller_attempt_claims_missing");
|
|
1236
|
+
}
|
|
1237
|
+
return {
|
|
1238
|
+
accountId: grant.accountId,
|
|
1239
|
+
workspaceId: grant.workspaceId,
|
|
1240
|
+
callerSessionId,
|
|
1241
|
+
callerTurnId: turnId,
|
|
1242
|
+
callerAttemptId: attemptId,
|
|
1243
|
+
callerExecutionGeneration: executionGeneration,
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1238
1247
|
function registerWorkspaceOrchestrationTools(
|
|
1239
1248
|
server: McpServer,
|
|
1240
1249
|
deps: ApiRouteDeps,
|
|
1241
1250
|
grant: AccessGrant,
|
|
1242
1251
|
can: (permission: Permission) => boolean,
|
|
1243
1252
|
callerSessionId: string | null,
|
|
1253
|
+
toolspaceMode: boolean,
|
|
1244
1254
|
json: JsonResult,
|
|
1245
1255
|
): void {
|
|
1246
1256
|
if (can("sessions:read")) {
|
|
@@ -1266,7 +1276,11 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1266
1276
|
if (!session) {
|
|
1267
1277
|
throw new Error("session not found");
|
|
1268
1278
|
}
|
|
1269
|
-
|
|
1279
|
+
const queue = await getSessionQueueSnapshot(deps.db, grant.workspaceId, sessionId);
|
|
1280
|
+
return json({
|
|
1281
|
+
...capSessionDetail(session),
|
|
1282
|
+
effectiveControl: queue?.effectiveControl ?? null,
|
|
1283
|
+
});
|
|
1270
1284
|
},
|
|
1271
1285
|
);
|
|
1272
1286
|
|
|
@@ -1384,7 +1398,7 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1384
1398
|
);
|
|
1385
1399
|
}
|
|
1386
1400
|
|
|
1387
|
-
if (can("sessions:control")) {
|
|
1401
|
+
if (can("sessions:control") && !toolspaceMode) {
|
|
1388
1402
|
server.registerTool(
|
|
1389
1403
|
"session_send_message",
|
|
1390
1404
|
{
|
|
@@ -1393,50 +1407,30 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1393
1407
|
inputSchema: {
|
|
1394
1408
|
sessionId: z4.string().uuid(),
|
|
1395
1409
|
text: z4.string().min(1),
|
|
1410
|
+
idempotencyKey: z4.string().uuid(),
|
|
1396
1411
|
// Header-value rotation only. URL/name/tool settings are immutable
|
|
1397
1412
|
// after create; core enforces mcp_servers:attach on this field.
|
|
1398
1413
|
mcpCredentialUpdates: z4.array(z4.unknown()).optional(),
|
|
1399
1414
|
},
|
|
1400
1415
|
},
|
|
1401
|
-
async ({ sessionId: targetSessionId, text, mcpCredentialUpdates }) => {
|
|
1416
|
+
async ({ sessionId: targetSessionId, text, idempotencyKey, mcpCredentialUpdates }) => {
|
|
1402
1417
|
if (callerSessionId !== null) {
|
|
1403
1418
|
if ((mcpCredentialUpdates?.length ?? 0) > 0) {
|
|
1404
1419
|
throw new Error("internal session updates cannot change MCP credentials");
|
|
1405
1420
|
}
|
|
1406
|
-
const result = await
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
classification: "info",
|
|
1412
|
-
sourceId: callerSessionId,
|
|
1413
|
-
dedupeKey: `session-message:${callerSessionId}:${crypto.randomUUID()}`,
|
|
1414
|
-
summary: text,
|
|
1415
|
-
payload: { text },
|
|
1416
|
-
lineage: { sourceSessionId: callerSessionId, targetSessionId },
|
|
1417
|
-
});
|
|
1418
|
-
if (result.reason === "session_cancelled") {
|
|
1419
|
-
return json({ delivered: false, reason: result.reason });
|
|
1420
|
-
}
|
|
1421
|
-
if (result.added && result.events.length > 0) {
|
|
1422
|
-
await deps.bus.publish(grant.workspaceId, targetSessionId, result.events);
|
|
1423
|
-
}
|
|
1424
|
-
if (result.shouldWake) {
|
|
1425
|
-
if (result.workflowWakeRevision === null) {
|
|
1426
|
-
throw new Error("Internal update has no workflow wake revision");
|
|
1427
|
-
}
|
|
1428
|
-
await deps.workflowClient.wakeSessionWorkflow({
|
|
1429
|
-
accountId: grant.accountId,
|
|
1430
|
-
workspaceId: grant.workspaceId,
|
|
1431
|
-
sessionId: targetSessionId,
|
|
1432
|
-
workflowId: result.temporalWorkflowId ?? workflowIdForSession(targetSessionId),
|
|
1433
|
-
wakeRevision: result.workflowWakeRevision,
|
|
1434
|
-
});
|
|
1435
|
-
}
|
|
1421
|
+
const result = await sendAgentSessionMessage(
|
|
1422
|
+
deps,
|
|
1423
|
+
exactAgentCommandContext(grant, callerSessionId),
|
|
1424
|
+
{ targetSessionId, text, idempotencyKey },
|
|
1425
|
+
);
|
|
1436
1426
|
return json({
|
|
1437
1427
|
delivered: true,
|
|
1438
|
-
updateId: result.
|
|
1428
|
+
updateId: result.updateId,
|
|
1439
1429
|
delivery: "coalesced_internal_update",
|
|
1430
|
+
effectiveState: result.effectiveState,
|
|
1431
|
+
wakeRequested: result.wakeRevision !== null,
|
|
1432
|
+
resumeRequired: result.effectiveState === "paused",
|
|
1433
|
+
replay: result.replay,
|
|
1440
1434
|
});
|
|
1441
1435
|
}
|
|
1442
1436
|
const { accepted, turn } = await acceptSessionUserMessage(
|
|
@@ -1447,8 +1441,9 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1447
1441
|
{
|
|
1448
1442
|
text,
|
|
1449
1443
|
toolsProvided: false,
|
|
1450
|
-
delivery: "
|
|
1444
|
+
delivery: "send",
|
|
1451
1445
|
origin: "operator",
|
|
1446
|
+
clientEventId: idempotencyKey,
|
|
1452
1447
|
mcpCredentialUpdates: (mcpCredentialUpdates ?? []).map((update) =>
|
|
1453
1448
|
SessionMcpCredentialUpdateInput.parse(update),
|
|
1454
1449
|
),
|
|
@@ -1464,39 +1459,126 @@ function registerWorkspaceOrchestrationTools(
|
|
|
1464
1459
|
description: "Pause this session. Waiting prompts stay saved and inert until Resume.",
|
|
1465
1460
|
inputSchema: {
|
|
1466
1461
|
sessionId: z4.string().uuid(),
|
|
1462
|
+
idempotencyKey: z4.string().uuid(),
|
|
1463
|
+
reason: z4.string().min(1).max(500).optional(),
|
|
1467
1464
|
},
|
|
1468
1465
|
},
|
|
1469
|
-
async ({ sessionId }) => {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
sessionId,
|
|
1487
|
-
eventId: controlled.event.id,
|
|
1488
|
-
workflowId: workflowIdForSession(sessionId),
|
|
1489
|
-
workflowWakeRevision: controlled.workflowWakeRevision,
|
|
1466
|
+
async ({ sessionId, idempotencyKey, reason }) => {
|
|
1467
|
+
if (callerSessionId !== null) {
|
|
1468
|
+
const controlled = await controlAgentSessionWorkstream(
|
|
1469
|
+
deps,
|
|
1470
|
+
exactAgentCommandContext(grant, callerSessionId),
|
|
1471
|
+
{
|
|
1472
|
+
targetSessionId: sessionId,
|
|
1473
|
+
action: "pause",
|
|
1474
|
+
idempotencyKey,
|
|
1475
|
+
reason: reason ?? "agent_mcp_pause",
|
|
1476
|
+
},
|
|
1477
|
+
);
|
|
1478
|
+
return json({
|
|
1479
|
+
receiptId: controlled.receipt.id,
|
|
1480
|
+
effectiveControl: controlled.control,
|
|
1481
|
+
interruptionCount: controlled.interruptionCount,
|
|
1482
|
+
replay: controlled.replay,
|
|
1490
1483
|
});
|
|
1491
1484
|
}
|
|
1492
|
-
return json(
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1485
|
+
return json(
|
|
1486
|
+
await controlHumanSessionWorkstream(
|
|
1487
|
+
deps,
|
|
1488
|
+
{
|
|
1489
|
+
accountId: grant.accountId,
|
|
1490
|
+
workspaceId: grant.workspaceId,
|
|
1491
|
+
sessionId,
|
|
1492
|
+
subjectId: grant.subjectId,
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
action: "pause",
|
|
1496
|
+
clientEventId: idempotencyKey,
|
|
1497
|
+
...(reason ? { reason } : {}),
|
|
1498
|
+
},
|
|
1499
|
+
),
|
|
1500
|
+
);
|
|
1497
1501
|
},
|
|
1498
1502
|
);
|
|
1499
1503
|
|
|
1504
|
+
server.registerTool(
|
|
1505
|
+
"session_resume",
|
|
1506
|
+
{
|
|
1507
|
+
description:
|
|
1508
|
+
"Resume the selected session workstream through older parent/workspace pauses. This creates no message.",
|
|
1509
|
+
inputSchema: {
|
|
1510
|
+
sessionId: z4.string().uuid(),
|
|
1511
|
+
idempotencyKey: z4.string().uuid(),
|
|
1512
|
+
reason: z4.string().min(1).max(500).optional(),
|
|
1513
|
+
},
|
|
1514
|
+
},
|
|
1515
|
+
async ({ sessionId, idempotencyKey, reason }) => {
|
|
1516
|
+
if (callerSessionId !== null) {
|
|
1517
|
+
const controlled = await controlAgentSessionWorkstream(
|
|
1518
|
+
deps,
|
|
1519
|
+
exactAgentCommandContext(grant, callerSessionId),
|
|
1520
|
+
{
|
|
1521
|
+
targetSessionId: sessionId,
|
|
1522
|
+
action: "resume",
|
|
1523
|
+
idempotencyKey,
|
|
1524
|
+
reason: reason ?? "agent_mcp_resume",
|
|
1525
|
+
},
|
|
1526
|
+
);
|
|
1527
|
+
return json({
|
|
1528
|
+
receiptId: controlled.receipt.id,
|
|
1529
|
+
effectiveControl: controlled.control,
|
|
1530
|
+
interruptionCount: controlled.interruptionCount,
|
|
1531
|
+
replay: controlled.replay,
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
return json(
|
|
1535
|
+
await controlHumanSessionWorkstream(
|
|
1536
|
+
deps,
|
|
1537
|
+
{
|
|
1538
|
+
accountId: grant.accountId,
|
|
1539
|
+
workspaceId: grant.workspaceId,
|
|
1540
|
+
sessionId,
|
|
1541
|
+
subjectId: grant.subjectId,
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
action: "resume",
|
|
1545
|
+
clientEventId: idempotencyKey,
|
|
1546
|
+
...(reason ? { reason } : {}),
|
|
1547
|
+
},
|
|
1548
|
+
),
|
|
1549
|
+
);
|
|
1550
|
+
},
|
|
1551
|
+
);
|
|
1552
|
+
|
|
1553
|
+
if (callerSessionId !== null) {
|
|
1554
|
+
server.registerTool(
|
|
1555
|
+
"session_steer",
|
|
1556
|
+
{
|
|
1557
|
+
description:
|
|
1558
|
+
"Atomically replace another session's current direction and resume it. The instruction is an internal update, never a human queue row.",
|
|
1559
|
+
inputSchema: {
|
|
1560
|
+
sessionId: z4.string().uuid(),
|
|
1561
|
+
instruction: z4.string().min(1),
|
|
1562
|
+
idempotencyKey: z4.string().uuid(),
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
async ({ sessionId, instruction, idempotencyKey }) => {
|
|
1566
|
+
const result = await steerAgentSession(
|
|
1567
|
+
deps,
|
|
1568
|
+
exactAgentCommandContext(grant, callerSessionId),
|
|
1569
|
+
{ targetSessionId: sessionId, instruction, idempotencyKey },
|
|
1570
|
+
);
|
|
1571
|
+
return json({
|
|
1572
|
+
updateId: result.updateId,
|
|
1573
|
+
interruptionCount: result.interruptionCount,
|
|
1574
|
+
stoppingPreviousAttempt: result.interruptionCount > 0,
|
|
1575
|
+
effectiveState: result.effectiveState,
|
|
1576
|
+
replay: result.replay,
|
|
1577
|
+
});
|
|
1578
|
+
},
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1500
1582
|
server.registerTool(
|
|
1501
1583
|
"set_other_session_title",
|
|
1502
1584
|
{
|