@opengeni/core 0.4.10 → 0.10.0
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/index.d.ts +165 -141
- package/dist/index.js +1132 -186
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/access/index.ts +4 -0
- package/src/application/session-commands.ts +192 -38
- package/src/billing/limits.ts +20 -1
- package/src/dependencies.ts +28 -2
- package/src/domain/capabilities.ts +171 -3
- package/src/domain/resources.ts +121 -31
- package/src/domain/scheduled-tasks.ts +4 -8
- package/src/domain/session-tool-policy.ts +211 -0
- package/src/domain/sessions.ts +658 -122
- package/src/index.ts +2 -0
- package/src/sandbox/fleet.ts +2 -2
- package/src/sandbox/routing.ts +1 -1
- package/src/session-authorization.ts +208 -0
package/dist/index.js
CHANGED
|
@@ -566,7 +566,9 @@ async function delegatedAccessContext(c, deps, mode, token = bearerToken(c)) {
|
|
|
566
566
|
...payload.turnId ? { turnId: payload.turnId } : {},
|
|
567
567
|
...payload.attemptId ? { attemptId: payload.attemptId } : {},
|
|
568
568
|
...payload.executionGeneration ? { executionGeneration: payload.executionGeneration } : {}
|
|
569
|
-
}
|
|
569
|
+
},
|
|
570
|
+
...payload.serviceInitiator ? { serviceInitiator: payload.serviceInitiator } : {},
|
|
571
|
+
...payload.serviceInitiatorContext ? { serviceInitiatorContext: payload.serviceInitiatorContext } : {}
|
|
570
572
|
}
|
|
571
573
|
],
|
|
572
574
|
defaultAccountId: payload.accountId,
|
|
@@ -586,6 +588,146 @@ async function sha256Hex(value) {
|
|
|
586
588
|
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
587
589
|
}
|
|
588
590
|
|
|
591
|
+
// src/session-authorization.ts
|
|
592
|
+
import {
|
|
593
|
+
SessionAuthorizationActor,
|
|
594
|
+
SessionAuthorizationDecision,
|
|
595
|
+
SessionAuthorizationListScope
|
|
596
|
+
} from "@opengeni/contracts";
|
|
597
|
+
import {
|
|
598
|
+
getSession,
|
|
599
|
+
getSessionRootId,
|
|
600
|
+
getSessionTurnForAttempt
|
|
601
|
+
} from "@opengeni/db";
|
|
602
|
+
var SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS = 15e3;
|
|
603
|
+
var SessionAuthorizationDeniedError = class extends Error {
|
|
604
|
+
constructor(reason) {
|
|
605
|
+
super("Session not found or access denied");
|
|
606
|
+
this.reason = reason;
|
|
607
|
+
this.name = "SessionAuthorizationDeniedError";
|
|
608
|
+
}
|
|
609
|
+
code = "SESSION_NOT_FOUND_OR_DENIED";
|
|
610
|
+
};
|
|
611
|
+
var SessionAuthorizationUnavailableError = class extends Error {
|
|
612
|
+
code = "SESSION_AUTHORIZATION_UNAVAILABLE";
|
|
613
|
+
constructor(options) {
|
|
614
|
+
super("Session authorization is unavailable", options);
|
|
615
|
+
this.name = "SessionAuthorizationUnavailableError";
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
async function requireSessionAuthorization(deps, grant, input) {
|
|
619
|
+
const port = deps.sessionAuthorization;
|
|
620
|
+
if (!port) return null;
|
|
621
|
+
const [actor, target] = await Promise.all([
|
|
622
|
+
resolveSessionAuthorizationActor(deps.db, grant),
|
|
623
|
+
resolveSessionAuthorizationTarget(deps.db, grant, input.sessionId)
|
|
624
|
+
]);
|
|
625
|
+
let rawDecision;
|
|
626
|
+
try {
|
|
627
|
+
rawDecision = await port.authorizeSession({
|
|
628
|
+
accountId: grant.accountId,
|
|
629
|
+
workspaceId: grant.workspaceId,
|
|
630
|
+
actor,
|
|
631
|
+
target,
|
|
632
|
+
operation: input.operation,
|
|
633
|
+
surface: input.surface
|
|
634
|
+
});
|
|
635
|
+
} catch (error) {
|
|
636
|
+
throw new SessionAuthorizationUnavailableError({ cause: error });
|
|
637
|
+
}
|
|
638
|
+
const parsed = SessionAuthorizationDecision.safeParse(rawDecision);
|
|
639
|
+
if (!parsed.success) {
|
|
640
|
+
throw new SessionAuthorizationUnavailableError({ cause: parsed.error });
|
|
641
|
+
}
|
|
642
|
+
if (!parsed.data.allowed) {
|
|
643
|
+
throw new SessionAuthorizationDeniedError(parsed.data.reason);
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
actor,
|
|
647
|
+
target,
|
|
648
|
+
relatedSessionAccess: parsed.data.relatedSessionAccess ?? "target",
|
|
649
|
+
reauthorizeAfterMs: parsed.data.reauthorizeAfterMs ?? null
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
async function requireSessionAuthorizationListScope(deps, grant, surface) {
|
|
653
|
+
const port = deps.sessionAuthorization;
|
|
654
|
+
if (!port) return null;
|
|
655
|
+
const actor = await resolveSessionAuthorizationActor(deps.db, grant);
|
|
656
|
+
let rawScope;
|
|
657
|
+
try {
|
|
658
|
+
rawScope = await port.resolveListScope({
|
|
659
|
+
accountId: grant.accountId,
|
|
660
|
+
workspaceId: grant.workspaceId,
|
|
661
|
+
actor,
|
|
662
|
+
surface
|
|
663
|
+
});
|
|
664
|
+
} catch (error) {
|
|
665
|
+
throw new SessionAuthorizationUnavailableError({ cause: error });
|
|
666
|
+
}
|
|
667
|
+
const parsed = SessionAuthorizationListScope.safeParse(rawScope);
|
|
668
|
+
if (!parsed.success) {
|
|
669
|
+
throw new SessionAuthorizationUnavailableError({ cause: parsed.error });
|
|
670
|
+
}
|
|
671
|
+
if (parsed.data.kind === "all") return parsed.data;
|
|
672
|
+
return {
|
|
673
|
+
kind: "scoped",
|
|
674
|
+
rootSessionIds: [...new Set(parsed.data.rootSessionIds)],
|
|
675
|
+
sessionIds: [...new Set(parsed.data.sessionIds)]
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
async function resolveSessionAuthorizationTarget(db, grant, sessionId) {
|
|
679
|
+
const session = await getSession(db, grant.workspaceId, sessionId);
|
|
680
|
+
if (!session || session.accountId !== grant.accountId) {
|
|
681
|
+
throw new SessionAuthorizationDeniedError("not_found");
|
|
682
|
+
}
|
|
683
|
+
let rootSessionId;
|
|
684
|
+
try {
|
|
685
|
+
rootSessionId = await getSessionRootId(db, grant.workspaceId, session.id);
|
|
686
|
+
} catch (error) {
|
|
687
|
+
throw new SessionAuthorizationUnavailableError({ cause: error });
|
|
688
|
+
}
|
|
689
|
+
if (!rootSessionId) {
|
|
690
|
+
throw new SessionAuthorizationDeniedError("not_found");
|
|
691
|
+
}
|
|
692
|
+
return { sessionId: session.id, rootSessionId };
|
|
693
|
+
}
|
|
694
|
+
async function resolveSessionAuthorizationActor(db, grant) {
|
|
695
|
+
const callerSessionId = grant.metadata?.["sessionId"];
|
|
696
|
+
const turnId = grant.metadata?.["turnId"];
|
|
697
|
+
const attemptId = grant.metadata?.["attemptId"];
|
|
698
|
+
const executionGeneration = grant.metadata?.["executionGeneration"];
|
|
699
|
+
const hasAttemptClaim = turnId !== void 0 || attemptId !== void 0 || executionGeneration !== void 0;
|
|
700
|
+
if (!hasAttemptClaim) {
|
|
701
|
+
return SessionAuthorizationActor.parse({
|
|
702
|
+
kind: "subject",
|
|
703
|
+
subjectId: grant.subjectId,
|
|
704
|
+
...grant.subjectLabel ? { subjectLabel: grant.subjectLabel } : {}
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
if (typeof callerSessionId !== "string" || typeof turnId !== "string" || typeof attemptId !== "string" || typeof executionGeneration !== "number" || !Number.isSafeInteger(executionGeneration) || executionGeneration < 1) {
|
|
708
|
+
throw new SessionAuthorizationDeniedError("caller_stale");
|
|
709
|
+
}
|
|
710
|
+
const [callerSession, turn, callerRootSessionId] = await Promise.all([
|
|
711
|
+
getSession(db, grant.workspaceId, callerSessionId),
|
|
712
|
+
getSessionTurnForAttempt(db, grant.workspaceId, callerSessionId, attemptId),
|
|
713
|
+
getSessionRootId(db, grant.workspaceId, callerSessionId).catch(() => null)
|
|
714
|
+
]);
|
|
715
|
+
if (!callerSession || callerSession.accountId !== grant.accountId || !turn || turn.id !== turnId || turn.executionGeneration !== executionGeneration || callerSession.activeTurnId !== turn.id || !callerRootSessionId) {
|
|
716
|
+
throw new SessionAuthorizationDeniedError("caller_stale");
|
|
717
|
+
}
|
|
718
|
+
return SessionAuthorizationActor.parse({
|
|
719
|
+
kind: "agent_attempt",
|
|
720
|
+
subjectId: grant.subjectId,
|
|
721
|
+
callerSessionId,
|
|
722
|
+
callerRootSessionId,
|
|
723
|
+
turnId,
|
|
724
|
+
attemptId,
|
|
725
|
+
executionGeneration,
|
|
726
|
+
initiator: turn.initiator,
|
|
727
|
+
initiatorContext: turn.initiatorContext
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
589
731
|
// src/billing/limits.ts
|
|
590
732
|
import { configuredStaticUsageLimits } from "@opengeni/config";
|
|
591
733
|
import {
|
|
@@ -748,6 +890,12 @@ async function recordWorkspaceUsage(deps, input) {
|
|
|
748
890
|
unit: input.unit,
|
|
749
891
|
sourceResourceType: input.sourceResourceType,
|
|
750
892
|
sourceResourceId: input.sourceResourceId,
|
|
893
|
+
sessionId: input.sessionId ?? null,
|
|
894
|
+
turnId: input.turnId ?? null,
|
|
895
|
+
turnAttemptId: input.turnAttemptId ?? null,
|
|
896
|
+
initiator: input.initiator ?? (input.subjectId ? { kind: "subject", subjectId: input.subjectId } : null),
|
|
897
|
+
...input.initiatorContext ? { initiatorContext: input.initiatorContext } : {},
|
|
898
|
+
origin: input.origin ?? null,
|
|
751
899
|
idempotencyKey: input.idempotencyKey
|
|
752
900
|
});
|
|
753
901
|
}
|
|
@@ -795,6 +943,10 @@ import {
|
|
|
795
943
|
upsertCapabilityCatalogItem
|
|
796
944
|
} from "@opengeni/db";
|
|
797
945
|
import { HTTPException as HTTPException6 } from "hono/http-exception";
|
|
946
|
+
import {
|
|
947
|
+
getSkillLibraryEntry,
|
|
948
|
+
listSkillLibraryEntries
|
|
949
|
+
} from "@opengeni/runtime/skill-library";
|
|
798
950
|
|
|
799
951
|
// src/domain/environments.ts
|
|
800
952
|
import { environmentsEncryptionKeyBytes } from "@opengeni/config";
|
|
@@ -1117,13 +1269,15 @@ async function buildCapabilityCatalog(input) {
|
|
|
1117
1269
|
capabilityInstallations,
|
|
1118
1270
|
packInstallations,
|
|
1119
1271
|
workspacePacks,
|
|
1120
|
-
bundledSkills
|
|
1272
|
+
bundledSkills,
|
|
1273
|
+
curatedLibrarySkills
|
|
1121
1274
|
] = await Promise.all([
|
|
1122
1275
|
listCapabilityCatalogItems(input.db, input.workspaceId),
|
|
1123
1276
|
listCapabilityInstallations(input.db, input.workspaceId),
|
|
1124
1277
|
listPackInstallations2(input.db, input.workspaceId),
|
|
1125
1278
|
listWorkspaceCapabilityPacks(input.db, input.workspaceId),
|
|
1126
|
-
discoverBundledSkills()
|
|
1279
|
+
discoverBundledSkills(),
|
|
1280
|
+
discoverCuratedSkillLibraryItems()
|
|
1127
1281
|
]);
|
|
1128
1282
|
const capabilityInstallationById = new Map(
|
|
1129
1283
|
capabilityInstallations.map((installation) => [installation.capabilityId, installation])
|
|
@@ -1138,7 +1292,8 @@ async function buildCapabilityCatalog(input) {
|
|
|
1138
1292
|
),
|
|
1139
1293
|
...configuredMcpCatalogItems(input.settings),
|
|
1140
1294
|
...platformApiCatalogItems(),
|
|
1141
|
-
...bundledSkills
|
|
1295
|
+
...bundledSkills,
|
|
1296
|
+
...curatedLibrarySkills
|
|
1142
1297
|
];
|
|
1143
1298
|
const items = dedupeCatalogItems([...builtIns, ...persistedItems]).map(
|
|
1144
1299
|
(item) => applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds)
|
|
@@ -1155,7 +1310,12 @@ async function createCatalogItem(input) {
|
|
|
1155
1310
|
message: "packs are managed by OpenGeni and cannot be manually created"
|
|
1156
1311
|
});
|
|
1157
1312
|
}
|
|
1158
|
-
|
|
1313
|
+
if (id.startsWith("skill:")) {
|
|
1314
|
+
throw new HTTPException6(422, {
|
|
1315
|
+
message: "skill ids are managed by the OpenGeni skill library or runtime adapters"
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
const source = input.payload.source === "built_in" || input.payload.source === "library" || input.payload.source === "configured" || input.payload.source === "registry" ? "manual" : input.payload.source;
|
|
1159
1319
|
const metadata = {
|
|
1160
1320
|
...input.payload.metadata,
|
|
1161
1321
|
...input.payload.kind === "mcp" && input.payload.endpointUrl && !input.payload.metadata.mcpServerId ? { mcpServerId: mcpServerIdForCapability(id, input.payload.metadata) } : {}
|
|
@@ -1190,11 +1350,46 @@ async function enableCapability(input) {
|
|
|
1190
1350
|
});
|
|
1191
1351
|
}
|
|
1192
1352
|
let installationMetadata = input.payload.metadata;
|
|
1193
|
-
|
|
1353
|
+
let installationConfig = { ...input.payload.config };
|
|
1194
1354
|
delete installationConfig.headers;
|
|
1195
1355
|
delete installationConfig.headersEncrypted;
|
|
1196
1356
|
delete installationConfig.headerNames;
|
|
1197
1357
|
delete installationConfig.connectionRef;
|
|
1358
|
+
if (item.kind === "skill" && item.source === "library") {
|
|
1359
|
+
const libraryId = stringMetadata(item.metadata.libraryId);
|
|
1360
|
+
const catalogVersion = stringMetadata(item.metadata.version);
|
|
1361
|
+
if (!libraryId || !catalogVersion) {
|
|
1362
|
+
throw new HTTPException6(422, {
|
|
1363
|
+
message: `skill library metadata is incomplete for ${item.id}`
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
const requestedVersion = input.payload.config.version;
|
|
1367
|
+
if (requestedVersion !== void 0 && typeof requestedVersion !== "string") {
|
|
1368
|
+
throw new HTTPException6(422, {
|
|
1369
|
+
message: "skill activation config.version must be a string"
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
const normalizedVersion = requestedVersion?.trim() || catalogVersion;
|
|
1373
|
+
if (normalizedVersion !== catalogVersion) {
|
|
1374
|
+
throw new HTTPException6(422, {
|
|
1375
|
+
message: `skill ${libraryId} only supports immutable version ${catalogVersion}`
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
const entry = getSkillLibraryEntry(libraryId, normalizedVersion);
|
|
1379
|
+
if (!entry) {
|
|
1380
|
+
throw new HTTPException6(422, {
|
|
1381
|
+
message: `skill library entry is unavailable: ${libraryId}@${normalizedVersion}`
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
installationConfig = { version: entry.version };
|
|
1385
|
+
installationMetadata = {
|
|
1386
|
+
libraryId: entry.id,
|
|
1387
|
+
libraryVersion: entry.version,
|
|
1388
|
+
contentSha256: entry.contentSha256,
|
|
1389
|
+
sourceCommit: entry.sourceCommit,
|
|
1390
|
+
provenance: entry.provenance
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1198
1393
|
if (item.kind === "mcp") {
|
|
1199
1394
|
const headers = await resolveMcpCredentialHeaders(input, item);
|
|
1200
1395
|
const connectionRef = input.payload.connectionRef ? await validateMcpCapabilityConnectionRef(input, item, input.payload.connectionRef) : null;
|
|
@@ -1353,9 +1548,11 @@ async function validateMcpCapabilityConnectionRef(input, item, ref) {
|
|
|
1353
1548
|
providerDomain: ref.providerDomain.trim(),
|
|
1354
1549
|
subjectScope: "workspace",
|
|
1355
1550
|
...ref.connectionId ? { connectionId: ref.connectionId } : {},
|
|
1551
|
+
...ref.provider ? { provider: ref.provider.trim() } : {},
|
|
1356
1552
|
...ref.kind ? { kind: ref.kind } : {},
|
|
1357
1553
|
...ref.scopes ? { scopes: uniqueStrings(ref.scopes) } : {},
|
|
1358
|
-
...ref.resource ? { resource: ref.resource } : {}
|
|
1554
|
+
...ref.resource ? { resource: ref.resource } : {},
|
|
1555
|
+
...ref.selectedResources ? { selectedResources: ref.selectedResources.map((resource) => ({ ...resource })) } : {}
|
|
1359
1556
|
};
|
|
1360
1557
|
if (!normalized.providerDomain) {
|
|
1361
1558
|
throw new HTTPException6(422, { message: "connectionRef.providerDomain is required" });
|
|
@@ -1504,12 +1701,20 @@ async function probeStreamableHttpMcpServer(input) {
|
|
|
1504
1701
|
function mcpProbeErrorMessage(error, endpointUrl) {
|
|
1505
1702
|
const message = error instanceof Error ? error.message : String(error);
|
|
1506
1703
|
const normalized = message.replace(/\s+/g, " ").trim();
|
|
1704
|
+
const endpoint = safeEndpointLabel(endpointUrl);
|
|
1507
1705
|
if (/404|405|not found|unexpected token|not valid json|invalid json|failed to parse|streamable http error|unable to connect|fetch failed|econnrefused|enotfound|timeout|aborted/i.test(
|
|
1508
1706
|
normalized
|
|
1509
1707
|
)) {
|
|
1510
|
-
return `OpenGeni could not reach a valid Streamable HTTP MCP server at ${
|
|
1708
|
+
return `OpenGeni could not reach a valid Streamable HTTP MCP server at ${endpoint}. Check the endpoint URL or choose a different catalog entry.`;
|
|
1709
|
+
}
|
|
1710
|
+
return `OpenGeni could not initialize ${endpoint}. Check the endpoint configuration or try again.`;
|
|
1711
|
+
}
|
|
1712
|
+
function safeEndpointLabel(endpointUrl) {
|
|
1713
|
+
try {
|
|
1714
|
+
return new URL(endpointUrl).hostname || "the configured endpoint";
|
|
1715
|
+
} catch {
|
|
1716
|
+
return "the configured endpoint";
|
|
1511
1717
|
}
|
|
1512
|
-
return `OpenGeni could not initialize ${endpointUrl}: ${normalized.slice(0, 500) || "unknown error"}`;
|
|
1513
1718
|
}
|
|
1514
1719
|
async function disableCapability(input) {
|
|
1515
1720
|
const item = await requireCatalogItem(
|
|
@@ -1799,6 +2004,43 @@ async function discoverBundledSkills() {
|
|
|
1799
2004
|
return [];
|
|
1800
2005
|
}
|
|
1801
2006
|
}
|
|
2007
|
+
async function discoverCuratedSkillLibraryItems() {
|
|
2008
|
+
return listSkillLibraryEntries().map((entry) => curatedSkillCatalogItem(entry));
|
|
2009
|
+
}
|
|
2010
|
+
function curatedSkillCatalogItem(entry) {
|
|
2011
|
+
return CapabilityCatalogItem.parse({
|
|
2012
|
+
id: `skill:${entry.id}`,
|
|
2013
|
+
kind: "skill",
|
|
2014
|
+
source: "library",
|
|
2015
|
+
name: entry.name,
|
|
2016
|
+
description: entry.description,
|
|
2017
|
+
category: entry.category,
|
|
2018
|
+
tags: [...entry.tags],
|
|
2019
|
+
homepageUrl: entry.sourceUrl,
|
|
2020
|
+
provenance: entry.provenance,
|
|
2021
|
+
tier: "verified",
|
|
2022
|
+
runtime: {
|
|
2023
|
+
available: true,
|
|
2024
|
+
notes: "Available as an explicit immutable opt-in skill selection."
|
|
2025
|
+
},
|
|
2026
|
+
metadata: {
|
|
2027
|
+
libraryId: entry.id,
|
|
2028
|
+
version: entry.version,
|
|
2029
|
+
contentSha256: entry.contentSha256,
|
|
2030
|
+
sourceCommit: entry.sourceCommit,
|
|
2031
|
+
sourceUrl: entry.sourceUrl,
|
|
2032
|
+
provenance: entry.provenance,
|
|
2033
|
+
license: entry.license,
|
|
2034
|
+
documentationUrl: entry.documentationUrl,
|
|
2035
|
+
compatibility: entry.compatibility,
|
|
2036
|
+
upgrade: entry.upgrade,
|
|
2037
|
+
artifactPath: entry.relativePath
|
|
2038
|
+
}
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
function stringMetadata(value) {
|
|
2042
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
2043
|
+
}
|
|
1802
2044
|
async function readSkillMetadata(url, fallbackName) {
|
|
1803
2045
|
const content = await readFile(url, "utf8");
|
|
1804
2046
|
const frontMatter = content.match(/^---\n([\s\S]*?)\n---/);
|
|
@@ -1827,6 +2069,15 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
|
|
|
1827
2069
|
enabledReason: item.source === "configured" ? "configured" : "built in"
|
|
1828
2070
|
};
|
|
1829
2071
|
}
|
|
2072
|
+
if (item.source === "library") {
|
|
2073
|
+
const enabled2 = installation?.status === "active" && skillLibraryInstallationRuntimeReady(item, installation);
|
|
2074
|
+
return {
|
|
2075
|
+
...item,
|
|
2076
|
+
enabled: enabled2,
|
|
2077
|
+
enabledReason: enabled2 ? "explicitly selected" : null,
|
|
2078
|
+
connectionRef: null
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
1830
2081
|
const activeInstallation = installation?.status === "active";
|
|
1831
2082
|
const enabled = !!activeInstallation && capabilityInstallationRuntimeReady(item, installation);
|
|
1832
2083
|
return {
|
|
@@ -1836,6 +2087,24 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
|
|
|
1836
2087
|
connectionRef: enabled && installation ? installationConnectionRef(installation.config) : null
|
|
1837
2088
|
};
|
|
1838
2089
|
}
|
|
2090
|
+
function skillLibraryInstallationRuntimeReady(item, installation) {
|
|
2091
|
+
if (item.kind !== "skill" || item.source !== "library" || installation.kind !== "skill") {
|
|
2092
|
+
return false;
|
|
2093
|
+
}
|
|
2094
|
+
const libraryId = stringMetadata(item.metadata.libraryId);
|
|
2095
|
+
const version = stringMetadata(item.metadata.version);
|
|
2096
|
+
const contentSha256 = stringMetadata(item.metadata.contentSha256);
|
|
2097
|
+
const sourceCommit = stringMetadata(item.metadata.sourceCommit);
|
|
2098
|
+
const provenance = stringMetadata(item.metadata.provenance);
|
|
2099
|
+
if (!libraryId || !version || !contentSha256 || !sourceCommit || !provenance) {
|
|
2100
|
+
return false;
|
|
2101
|
+
}
|
|
2102
|
+
const entry = getSkillLibraryEntry(libraryId, version);
|
|
2103
|
+
if (!entry || item.id !== `skill:${entry.id}` || installation.capabilityId !== `skill:${entry.id}` || contentSha256 !== entry.contentSha256 || sourceCommit !== entry.sourceCommit || provenance !== entry.provenance) {
|
|
2104
|
+
return false;
|
|
2105
|
+
}
|
|
2106
|
+
return installation.config.version === entry.version && stringMetadata(installation.metadata.libraryId) === entry.id && stringMetadata(installation.metadata.libraryVersion) === entry.version && stringMetadata(installation.metadata.contentSha256) === entry.contentSha256 && stringMetadata(installation.metadata.sourceCommit) === entry.sourceCommit && stringMetadata(installation.metadata.provenance) === entry.provenance;
|
|
2107
|
+
}
|
|
1839
2108
|
function installationConnectionRef(config) {
|
|
1840
2109
|
const ref = config.connectionRef;
|
|
1841
2110
|
if (!ref || typeof ref !== "object") {
|
|
@@ -1850,6 +2119,14 @@ function installationConnectionRef(config) {
|
|
|
1850
2119
|
function dedupeCatalogItems(items) {
|
|
1851
2120
|
const byId = /* @__PURE__ */ new Map();
|
|
1852
2121
|
for (const item of items) {
|
|
2122
|
+
if (item.kind === "skill" && item.source === "library" && byId.has(item.id)) {
|
|
2123
|
+
byId.set(item.id, item);
|
|
2124
|
+
continue;
|
|
2125
|
+
}
|
|
2126
|
+
const existing = byId.get(item.id);
|
|
2127
|
+
if (existing?.kind === "skill" && existing.source === "library") {
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
1853
2130
|
byId.set(item.id, item);
|
|
1854
2131
|
}
|
|
1855
2132
|
return [...byId.values()];
|
|
@@ -2377,13 +2654,20 @@ async function listRigChangesForApi(deps, workspaceId, rigId, limit) {
|
|
|
2377
2654
|
|
|
2378
2655
|
// src/domain/resources.ts
|
|
2379
2656
|
import {
|
|
2657
|
+
gitCredentialBindingIdForRepository,
|
|
2658
|
+
gitCredentialProviderForRepository,
|
|
2659
|
+
defaultRepositoryMountPath,
|
|
2380
2660
|
mergeResourceRefs as mergeContractResourceRefs,
|
|
2381
2661
|
mergeToolRefs,
|
|
2662
|
+
normalizeRepositorySubpath,
|
|
2663
|
+
normalizeResourceMountPath,
|
|
2382
2664
|
resourceIdentityKey,
|
|
2665
|
+
resourceMountPathCollisionKey,
|
|
2383
2666
|
ResourceRefConflictError,
|
|
2667
|
+
ResourceMountPathError,
|
|
2384
2668
|
stableJson
|
|
2385
2669
|
} from "@opengeni/contracts";
|
|
2386
|
-
import {
|
|
2670
|
+
import { areGitHubRepositoriesAllowedForWorkspace, requireFile } from "@opengeni/db";
|
|
2387
2671
|
import { HTTPException as HTTPException8 } from "hono/http-exception";
|
|
2388
2672
|
function validateToolRefs(tools, settings) {
|
|
2389
2673
|
const mcpServerIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
@@ -2414,9 +2698,26 @@ function enabledCapabilityMcpToolRefs(settings, runtimeSettings) {
|
|
|
2414
2698
|
function withDefaultEnabledCapabilityMcpTools(tools, settings, runtimeSettings) {
|
|
2415
2699
|
return mergeToolRefs(tools, enabledCapabilityMcpToolRefs(settings, runtimeSettings));
|
|
2416
2700
|
}
|
|
2701
|
+
function availableToolRefs(tools, settings) {
|
|
2702
|
+
const available = new Set(settings.mcpServers.map((server) => server.id));
|
|
2703
|
+
return tools.filter((tool) => available.has(tool.id));
|
|
2704
|
+
}
|
|
2705
|
+
function assertToolRefsSubset(requested, allowed, message = "requested tools exceed the session tool policy") {
|
|
2706
|
+
const allowedIds = new Set(allowed.map((tool) => `${tool.kind}:${tool.id}`));
|
|
2707
|
+
const widened = requested.find((tool) => !allowedIds.has(`${tool.kind}:${tool.id}`));
|
|
2708
|
+
if (widened) {
|
|
2709
|
+
throw new HTTPException8(403, { message: `${message}: ${widened.id}` });
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
function validateToolRefsForSessionPolicy(input) {
|
|
2713
|
+
const validated = validateToolRefs(input.requested, input.settings);
|
|
2714
|
+
assertToolRefsSubset(validated, input.allowedTools, input.message);
|
|
2715
|
+
return validated;
|
|
2716
|
+
}
|
|
2417
2717
|
function normalizeResources(resources) {
|
|
2418
2718
|
const mountPaths = /* @__PURE__ */ new Map();
|
|
2419
2719
|
const identities = /* @__PURE__ */ new Map();
|
|
2720
|
+
const credentialBindingProviders = /* @__PURE__ */ new Map();
|
|
2420
2721
|
const seenResources = /* @__PURE__ */ new Set();
|
|
2421
2722
|
const out = [];
|
|
2422
2723
|
for (const resource of resources) {
|
|
@@ -2439,14 +2740,35 @@ function normalizeResources(resources) {
|
|
|
2439
2740
|
throw new HTTPException8(422, { message: "repository URL must include owner and repo" });
|
|
2440
2741
|
}
|
|
2441
2742
|
const repo = parts.join("/");
|
|
2442
|
-
const
|
|
2743
|
+
const normalizedUri = `https://${url.host.toLowerCase()}/${repo}.git`;
|
|
2744
|
+
const mountPath = normalizeMountPath(
|
|
2745
|
+
resource.mountPath ?? defaultRepositoryMountPath(normalizedUri)
|
|
2746
|
+
);
|
|
2747
|
+
const credentialProvider = gitCredentialProviderForRepository(resource);
|
|
2748
|
+
const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
|
|
2749
|
+
if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
|
|
2750
|
+
throw new HTTPException8(422, {
|
|
2751
|
+
message: "repository credential bindings and access intent require a Git provider"
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
if (credentialProvider && credentialBindingId) {
|
|
2755
|
+
const boundProvider = credentialBindingProviders.get(credentialBindingId);
|
|
2756
|
+
if (boundProvider && boundProvider !== credentialProvider) {
|
|
2757
|
+
throw new HTTPException8(422, {
|
|
2758
|
+
message: `credential binding ${credentialBindingId} is assigned to multiple Git providers`
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
credentialBindingProviders.set(credentialBindingId, credentialProvider);
|
|
2762
|
+
}
|
|
2443
2763
|
normalized = {
|
|
2444
2764
|
kind: "repository",
|
|
2445
|
-
uri:
|
|
2765
|
+
uri: normalizedUri,
|
|
2446
2766
|
ref: resource.ref.trim(),
|
|
2447
2767
|
mountPath,
|
|
2448
|
-
...resource.subpath ? { subpath:
|
|
2768
|
+
...resource.subpath ? { subpath: normalizeRepositorySubpath(resource.subpath) } : {},
|
|
2449
2769
|
...resource.provider ? { provider: resource.provider } : {},
|
|
2770
|
+
...resource.credentialBindingId ? { credentialBindingId: resource.credentialBindingId } : {},
|
|
2771
|
+
...resource.access ? { access: resource.access } : {},
|
|
2450
2772
|
...resource.repositoryId !== void 0 ? { repositoryId: resource.repositoryId } : {},
|
|
2451
2773
|
...resource.installationId !== void 0 ? { installationId: resource.installationId } : {},
|
|
2452
2774
|
...resource.projectId !== void 0 ? { projectId: resource.projectId } : {},
|
|
@@ -2456,14 +2778,15 @@ function normalizeResources(resources) {
|
|
|
2456
2778
|
};
|
|
2457
2779
|
}
|
|
2458
2780
|
const key = stableJson(normalized);
|
|
2459
|
-
const
|
|
2781
|
+
const mountCollisionKey = normalized.mountPath ? resourceMountPathCollisionKey(normalized.mountPath) : void 0;
|
|
2782
|
+
const mounted = mountCollisionKey ? mountPaths.get(mountCollisionKey) : void 0;
|
|
2460
2783
|
if (mounted && mounted !== key) {
|
|
2461
2784
|
throw new HTTPException8(422, {
|
|
2462
2785
|
message: `duplicate resource mount path: ${normalized.mountPath}`
|
|
2463
2786
|
});
|
|
2464
2787
|
}
|
|
2465
2788
|
if (normalized.mountPath) {
|
|
2466
|
-
mountPaths.set(
|
|
2789
|
+
mountPaths.set(mountCollisionKey, key);
|
|
2467
2790
|
}
|
|
2468
2791
|
const identity = resourceIdentityKey(normalized);
|
|
2469
2792
|
const seenIdentity = identities.get(identity);
|
|
@@ -2490,8 +2813,24 @@ function mergeResourceRefs(existing, additions) {
|
|
|
2490
2813
|
throw error;
|
|
2491
2814
|
}
|
|
2492
2815
|
}
|
|
2816
|
+
function validateGitHubRepositorySelectionShapes(resources) {
|
|
2817
|
+
const selected = gitHubRepositorySelections(resources);
|
|
2818
|
+
if (selected.length === 0) {
|
|
2819
|
+
return [];
|
|
2820
|
+
}
|
|
2821
|
+
return [...new Set(selected.map((item) => item.installationId))];
|
|
2822
|
+
}
|
|
2493
2823
|
function validateGitHubRepositorySelectionShape(resources) {
|
|
2494
|
-
const
|
|
2824
|
+
const installationIds = validateGitHubRepositorySelectionShapes(resources);
|
|
2825
|
+
if (installationIds.length > 1) {
|
|
2826
|
+
throw new HTTPException8(422, {
|
|
2827
|
+
message: "GitHub App repository resources must belong to one installation"
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
return installationIds[0] ?? null;
|
|
2831
|
+
}
|
|
2832
|
+
function gitHubRepositorySelections(resources) {
|
|
2833
|
+
return resources.flatMap((resource) => {
|
|
2495
2834
|
if (resource.kind !== "repository") {
|
|
2496
2835
|
return [];
|
|
2497
2836
|
}
|
|
@@ -2503,38 +2842,34 @@ function validateGitHubRepositorySelectionShape(resources) {
|
|
|
2503
2842
|
if (installationRaw === void 0 && repositoryRaw === void 0) {
|
|
2504
2843
|
return [];
|
|
2505
2844
|
}
|
|
2506
|
-
const
|
|
2845
|
+
const installationId = positiveInteger(installationRaw);
|
|
2507
2846
|
const repositoryId = positiveInteger(repositoryRaw);
|
|
2508
|
-
if (!
|
|
2847
|
+
if (!installationId || !repositoryId) {
|
|
2509
2848
|
throw new HTTPException8(422, {
|
|
2510
2849
|
message: "GitHub App repository resources require positive github_installation_id and github_repository_id"
|
|
2511
2850
|
});
|
|
2512
2851
|
}
|
|
2513
|
-
return [{ installationId
|
|
2852
|
+
return [{ installationId, repositoryId }];
|
|
2514
2853
|
});
|
|
2515
|
-
if (selected.length === 0) {
|
|
2516
|
-
return null;
|
|
2517
|
-
}
|
|
2518
|
-
const installationId = selected[0].installationId;
|
|
2519
|
-
if (selected.some((item) => item.installationId !== installationId)) {
|
|
2520
|
-
throw new HTTPException8(422, {
|
|
2521
|
-
message: "GitHub App repository resources must belong to one installation"
|
|
2522
|
-
});
|
|
2523
|
-
}
|
|
2524
|
-
return installationId;
|
|
2525
2854
|
}
|
|
2526
2855
|
async function validateGitHubRepositorySelection(db, workspaceId, resources) {
|
|
2527
|
-
const
|
|
2528
|
-
if (
|
|
2856
|
+
const installationIds = validateGitHubRepositorySelectionShapes(resources);
|
|
2857
|
+
if (installationIds.length === 0) {
|
|
2529
2858
|
return;
|
|
2530
2859
|
}
|
|
2531
|
-
const
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2860
|
+
const selections = gitHubRepositorySelections(resources);
|
|
2861
|
+
for (const installationId of installationIds) {
|
|
2862
|
+
const repositoryIds = selections.filter((selection) => selection.installationId === installationId).map((selection) => selection.repositoryId);
|
|
2863
|
+
if (!await areGitHubRepositoriesAllowedForWorkspace(
|
|
2864
|
+
db,
|
|
2865
|
+
workspaceId,
|
|
2866
|
+
installationId,
|
|
2867
|
+
repositoryIds
|
|
2868
|
+
)) {
|
|
2869
|
+
throw new HTTPException8(422, {
|
|
2870
|
+
message: "GitHub App repository resources must be authorized for a GitHub App installation linked to this workspace"
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2538
2873
|
}
|
|
2539
2874
|
}
|
|
2540
2875
|
async function validateFileResources(db, workspaceId, resources) {
|
|
@@ -2559,11 +2894,12 @@ async function validateFileResources(db, workspaceId, resources) {
|
|
|
2559
2894
|
}
|
|
2560
2895
|
}
|
|
2561
2896
|
function normalizeMountPath(path) {
|
|
2562
|
-
|
|
2563
|
-
|
|
2897
|
+
try {
|
|
2898
|
+
return normalizeResourceMountPath(path);
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
if (!(error instanceof ResourceMountPathError)) throw error;
|
|
2564
2901
|
throw new HTTPException8(422, { message: `invalid resource mount path: ${path}` });
|
|
2565
2902
|
}
|
|
2566
|
-
return normalized;
|
|
2567
2903
|
}
|
|
2568
2904
|
function parseResourceUrl(uri) {
|
|
2569
2905
|
try {
|
|
@@ -2582,6 +2918,128 @@ function positiveInteger(value) {
|
|
|
2582
2918
|
return null;
|
|
2583
2919
|
}
|
|
2584
2920
|
|
|
2921
|
+
// src/domain/session-tool-policy.ts
|
|
2922
|
+
import {
|
|
2923
|
+
SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT,
|
|
2924
|
+
SESSION_EFFECTIVE_TOOL_POLICY_ID_MAX_LENGTH,
|
|
2925
|
+
mergeToolRefs as mergeToolRefs2
|
|
2926
|
+
} from "@opengeni/contracts";
|
|
2927
|
+
var MANDATORY_SESSION_MCP_SERVER_IDS = ["opengeni"];
|
|
2928
|
+
var PROJECTABLE_REGISTRY_ID = /^[A-Za-z0-9_-]+$/;
|
|
2929
|
+
function sortedIds(ids) {
|
|
2930
|
+
return [...new Set(ids)].sort();
|
|
2931
|
+
}
|
|
2932
|
+
function projectIds(ids) {
|
|
2933
|
+
const projectable = ids.filter(
|
|
2934
|
+
(id) => id.length <= SESSION_EFFECTIVE_TOOL_POLICY_ID_MAX_LENGTH && PROJECTABLE_REGISTRY_ID.test(id)
|
|
2935
|
+
);
|
|
2936
|
+
return {
|
|
2937
|
+
ids: projectable.slice(0, SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT),
|
|
2938
|
+
truncated: projectable.length !== ids.length || projectable.length > SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
function resolveSessionToolPolicy(input) {
|
|
2942
|
+
const policy = input.toolPolicy ?? { mode: "legacy", inheritedFromSessionId: null };
|
|
2943
|
+
const availableIds = new Set(input.availableMcpServerIds);
|
|
2944
|
+
const defaultIds = new Set(input.defaultMcpServerIds ?? []);
|
|
2945
|
+
const mandatoryIds = MANDATORY_SESSION_MCP_SERVER_IDS.filter(
|
|
2946
|
+
(id) => availableIds.has(id)
|
|
2947
|
+
);
|
|
2948
|
+
const mandatoryIdSet = new Set(mandatoryIds);
|
|
2949
|
+
const selectedRefs = input.turnToolsProvided === true ? mergeToolRefs2([], input.turnTools ?? []) : input.turnToolsProvided === false ? mergeToolRefs2([], input.sessionTools) : mergeToolRefs2(input.sessionTools, input.turnTools ?? []);
|
|
2950
|
+
const tracksWorkspaceDefaults = policy.mode === "workspace_default" && input.turnToolsProvided !== true;
|
|
2951
|
+
let toolRefs = selectedRefs.filter((tool) => tool.optional !== true || availableIds.has(tool.id));
|
|
2952
|
+
if (tracksWorkspaceDefaults) {
|
|
2953
|
+
toolRefs = mergeToolRefs2(
|
|
2954
|
+
toolRefs,
|
|
2955
|
+
sortedIds(defaultIds).filter((id) => availableIds.has(id)).map((id) => ({ kind: "mcp", id, optional: true }))
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2958
|
+
toolRefs = mergeToolRefs2(
|
|
2959
|
+
toolRefs,
|
|
2960
|
+
mandatoryIds.map((id) => ({ kind: "mcp", id }))
|
|
2961
|
+
);
|
|
2962
|
+
const requestedEffectiveRefs = mergeToolRefs2(
|
|
2963
|
+
selectedRefs,
|
|
2964
|
+
tracksWorkspaceDefaults ? sortedIds(defaultIds).filter((id) => availableIds.has(id)).map((id) => ({ kind: "mcp", id, optional: true })) : []
|
|
2965
|
+
);
|
|
2966
|
+
const effectiveIds = sortedIds(
|
|
2967
|
+
mergeToolRefs2(
|
|
2968
|
+
requestedEffectiveRefs,
|
|
2969
|
+
mandatoryIds.map((id) => ({ kind: "mcp", id }))
|
|
2970
|
+
).map((tool) => tool.id)
|
|
2971
|
+
);
|
|
2972
|
+
const configuredIds = effectiveIds.filter((id) => availableIds.has(id));
|
|
2973
|
+
const configuredIdSet = new Set(configuredIds);
|
|
2974
|
+
const droppedIds = effectiveIds.filter((id) => !configuredIdSet.has(id));
|
|
2975
|
+
const deferredIds = tracksWorkspaceDefaults ? sortedIds(
|
|
2976
|
+
toolRefs.filter(
|
|
2977
|
+
(tool) => tool.optional === true && configuredIdSet.has(tool.id) && !mandatoryIdSet.has(tool.id)
|
|
2978
|
+
).map((tool) => tool.id)
|
|
2979
|
+
) : [];
|
|
2980
|
+
const selectedIds = sortedIds(
|
|
2981
|
+
selectedRefs.filter(
|
|
2982
|
+
(tool) => !mandatoryIdSet.has(tool.id) && !(tracksWorkspaceDefaults && tool.optional === true)
|
|
2983
|
+
).map((tool) => tool.id)
|
|
2984
|
+
);
|
|
2985
|
+
const projections = {
|
|
2986
|
+
selected: projectIds(selectedIds),
|
|
2987
|
+
effective: projectIds(effectiveIds),
|
|
2988
|
+
mandatory: projectIds(sortedIds(mandatoryIds)),
|
|
2989
|
+
deferred: projectIds(deferredIds),
|
|
2990
|
+
configured: projectIds(configuredIds),
|
|
2991
|
+
dropped: projectIds(droppedIds)
|
|
2992
|
+
};
|
|
2993
|
+
return {
|
|
2994
|
+
toolRefs,
|
|
2995
|
+
effectivePolicy: {
|
|
2996
|
+
mode: policy.mode,
|
|
2997
|
+
inheritedFromSessionId: policy.inheritedFromSessionId,
|
|
2998
|
+
selectedIds: projections.selected.ids,
|
|
2999
|
+
effectiveIds: projections.effective.ids,
|
|
3000
|
+
mandatoryIds: projections.mandatory.ids,
|
|
3001
|
+
lazyRouter: {
|
|
3002
|
+
state: tracksWorkspaceDefaults ? "required" : "disabled",
|
|
3003
|
+
deferredIds: projections.deferred.ids
|
|
3004
|
+
},
|
|
3005
|
+
configuredIds: projections.configured.ids,
|
|
3006
|
+
droppedIds: projections.dropped.ids,
|
|
3007
|
+
counts: {
|
|
3008
|
+
selected: selectedIds.length,
|
|
3009
|
+
effective: effectiveIds.length,
|
|
3010
|
+
mandatory: mandatoryIds.length,
|
|
3011
|
+
deferred: deferredIds.length,
|
|
3012
|
+
configured: configuredIds.length,
|
|
3013
|
+
dropped: droppedIds.length
|
|
3014
|
+
},
|
|
3015
|
+
idsTruncated: Object.values(projections).some((projection) => projection.truncated)
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
async function workspaceSessionToolPolicyServerIds(db, workspaceId, settings) {
|
|
3020
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
|
|
3021
|
+
return sortedIds(runtimeSettings.mcpServers.map((server) => server.id));
|
|
3022
|
+
}
|
|
3023
|
+
async function workspaceSessionToolPolicyDefaultServerIds(db, workspaceId, settings) {
|
|
3024
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
|
|
3025
|
+
return sortedIds(enabledCapabilityMcpToolRefs(settings, runtimeSettings).map((tool) => tool.id));
|
|
3026
|
+
}
|
|
3027
|
+
function sessionWithEffectiveToolPolicy(session, workspaceServerIds, workspaceDefaultServerIds = []) {
|
|
3028
|
+
const availableIds = new Set(workspaceServerIds);
|
|
3029
|
+
for (const server of session.mcpServers) {
|
|
3030
|
+
availableIds.add(server.id);
|
|
3031
|
+
}
|
|
3032
|
+
return {
|
|
3033
|
+
...session,
|
|
3034
|
+
effectiveToolPolicy: resolveSessionToolPolicy({
|
|
3035
|
+
...session.toolPolicy ? { toolPolicy: session.toolPolicy } : {},
|
|
3036
|
+
sessionTools: session.tools,
|
|
3037
|
+
availableMcpServerIds: availableIds,
|
|
3038
|
+
defaultMcpServerIds: workspaceDefaultServerIds
|
|
3039
|
+
}).effectivePolicy
|
|
3040
|
+
};
|
|
3041
|
+
}
|
|
3042
|
+
|
|
2585
3043
|
// src/domain/scheduled-tasks.ts
|
|
2586
3044
|
import {
|
|
2587
3045
|
createScheduledTask,
|
|
@@ -2594,11 +3052,20 @@ import { HTTPException as HTTPException10 } from "hono/http-exception";
|
|
|
2594
3052
|
|
|
2595
3053
|
// src/domain/sessions.ts
|
|
2596
3054
|
import { CODEX_MODEL_ID_PREFIX } from "@opengeni/codex";
|
|
2597
|
-
import {
|
|
3055
|
+
import {
|
|
3056
|
+
canonicalizeConfiguredModelId,
|
|
3057
|
+
configuredAllowedModels,
|
|
3058
|
+
policyProviderIdForModel,
|
|
3059
|
+
resolveTurnExecutionPolicyV1
|
|
3060
|
+
} from "@opengeni/config";
|
|
2598
3061
|
import {
|
|
2599
3062
|
CreateSessionRequest,
|
|
3063
|
+
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
|
|
3064
|
+
ServiceTurnInitiator,
|
|
3065
|
+
ServiceTurnInitiatorContext,
|
|
2600
3066
|
evaluateWorkspaceModelPolicy,
|
|
2601
|
-
reasoningEffortForMetadata
|
|
3067
|
+
reasoningEffortForMetadata,
|
|
3068
|
+
SessionMcpApprovalPolicy
|
|
2602
3069
|
} from "@opengeni/contracts";
|
|
2603
3070
|
import {
|
|
2604
3071
|
createSession,
|
|
@@ -2611,7 +3078,8 @@ import {
|
|
|
2611
3078
|
listDistinctVariableSetIdsInGroup,
|
|
2612
3079
|
listDistinctRigVersionIdsInGroup,
|
|
2613
3080
|
getSandbox as getSandbox3,
|
|
2614
|
-
getSession,
|
|
3081
|
+
getSession as getSession2,
|
|
3082
|
+
SessionIdConflictError,
|
|
2615
3083
|
getSessionByCreateIdempotencyKey,
|
|
2616
3084
|
getSessionEvent,
|
|
2617
3085
|
getWorkspaceControlEvent,
|
|
@@ -2619,11 +3087,15 @@ import {
|
|
|
2619
3087
|
getSessionTurn,
|
|
2620
3088
|
getWorkspaceModelPolicy,
|
|
2621
3089
|
initializeSessionStartAtomically,
|
|
3090
|
+
listSessionTurns,
|
|
3091
|
+
listSessionMcpServersForChildInheritance,
|
|
2622
3092
|
requireSession as requireSession2,
|
|
2623
3093
|
submitHumanPromptInTransaction,
|
|
3094
|
+
appendSessionEventsWithLockedSessionUpdate,
|
|
2624
3095
|
updateSessionTitle as updateSessionTitleRow,
|
|
2625
3096
|
withWorkspaceSubjectRls,
|
|
2626
3097
|
QueueCommandConflictError,
|
|
3098
|
+
AgentCommandAuthorityError,
|
|
2627
3099
|
SessionControlConflictError
|
|
2628
3100
|
} from "@opengeni/db";
|
|
2629
3101
|
import {
|
|
@@ -2636,6 +3108,72 @@ var reservedSessionMcpServerIds = /* @__PURE__ */ new Set(["opengeni", "files",
|
|
|
2636
3108
|
var maxSessionMcpCredentialHeaders = 16;
|
|
2637
3109
|
var maxSessionMcpCredentialHeaderValueLength = 4096;
|
|
2638
3110
|
var sessionMcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
3111
|
+
function serviceInitiatorForGrant(grant) {
|
|
3112
|
+
if (!grant.serviceInitiator) {
|
|
3113
|
+
if (grant.serviceInitiatorContext) {
|
|
3114
|
+
throw new HTTPException9(403, {
|
|
3115
|
+
message: "service initiator context requires a signed service initiator"
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
const initiator = ServiceTurnInitiator.safeParse(grant.serviceInitiator);
|
|
3121
|
+
if (!initiator.success) {
|
|
3122
|
+
throw new HTTPException9(403, {
|
|
3123
|
+
message: "a delegated command initiator must be a bounded service principal"
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
const context = ServiceTurnInitiatorContext.safeParse(grant.serviceInitiatorContext ?? {});
|
|
3127
|
+
if (!context.success) {
|
|
3128
|
+
throw new HTTPException9(403, {
|
|
3129
|
+
message: "delegated service initiator context is invalid or reserved"
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
const callerTurnId = grant.metadata?.["turnId"];
|
|
3133
|
+
const callerAttemptId = grant.metadata?.["attemptId"];
|
|
3134
|
+
const callerExecutionGeneration = grant.metadata?.["executionGeneration"];
|
|
3135
|
+
if (callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0) {
|
|
3136
|
+
throw new HTTPException9(403, {
|
|
3137
|
+
message: "a service initiator cannot replace an exact agent-attempt initiator"
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
return {
|
|
3141
|
+
initiator: initiator.data,
|
|
3142
|
+
context: context.data
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
function creationInitiatorForGrant(grant) {
|
|
3146
|
+
const serviceInitiator = serviceInitiatorForGrant(grant);
|
|
3147
|
+
const callerSessionId = grant.metadata?.["sessionId"];
|
|
3148
|
+
const callerTurnId = grant.metadata?.["turnId"];
|
|
3149
|
+
const callerAttemptId = grant.metadata?.["attemptId"];
|
|
3150
|
+
const callerExecutionGeneration = grant.metadata?.["executionGeneration"];
|
|
3151
|
+
const hasCallerTurnClaim = callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0;
|
|
3152
|
+
if (hasCallerTurnClaim) {
|
|
3153
|
+
if (typeof callerSessionId !== "string" || typeof callerTurnId !== "string" || typeof callerAttemptId !== "string" || typeof callerExecutionGeneration !== "number" || !Number.isSafeInteger(callerExecutionGeneration) || callerExecutionGeneration < 1) {
|
|
3154
|
+
throw new HTTPException9(403, { message: "caller attempt claims are incomplete" });
|
|
3155
|
+
}
|
|
3156
|
+
const actor = {
|
|
3157
|
+
type: "agent_attempt",
|
|
3158
|
+
sessionId: callerSessionId,
|
|
3159
|
+
turnId: callerTurnId,
|
|
3160
|
+
attemptId: callerAttemptId,
|
|
3161
|
+
executionGeneration: callerExecutionGeneration
|
|
3162
|
+
};
|
|
3163
|
+
return { actor };
|
|
3164
|
+
}
|
|
3165
|
+
if (serviceInitiator) {
|
|
3166
|
+
return serviceInitiator;
|
|
3167
|
+
}
|
|
3168
|
+
return {
|
|
3169
|
+
initiator: {
|
|
3170
|
+
kind: "subject",
|
|
3171
|
+
subjectId: grant.subjectId,
|
|
3172
|
+
...grant.subjectLabel ? { label: grant.subjectLabel } : {}
|
|
3173
|
+
},
|
|
3174
|
+
context: {}
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
2639
3177
|
function normalizedSessionMcpCredentialHeaders(headers) {
|
|
2640
3178
|
if (!headers) {
|
|
2641
3179
|
return {};
|
|
@@ -2677,7 +3215,20 @@ function mcpServerConfigFromInput(server) {
|
|
|
2677
3215
|
...server.allowedTools ? { allowedTools: server.allowedTools } : {},
|
|
2678
3216
|
...server.timeoutMs ? { timeoutMs: server.timeoutMs } : {},
|
|
2679
3217
|
cacheToolsList: server.cacheToolsList ?? false,
|
|
2680
|
-
...server.requireApproval !== void 0 ? { requireApproval: server.requireApproval } : {}
|
|
3218
|
+
...server.requireApproval !== void 0 ? { requireApproval: server.requireApproval } : {},
|
|
3219
|
+
...server.connectionRef ? { connectionRef: server.connectionRef } : {}
|
|
3220
|
+
};
|
|
3221
|
+
}
|
|
3222
|
+
function mcpServerConfigFromStoredInput(server) {
|
|
3223
|
+
return {
|
|
3224
|
+
id: server.id,
|
|
3225
|
+
...server.name ? { name: server.name } : {},
|
|
3226
|
+
url: server.url,
|
|
3227
|
+
...server.allowedTools ? { allowedTools: server.allowedTools } : {},
|
|
3228
|
+
...server.timeoutMs ? { timeoutMs: server.timeoutMs } : {},
|
|
3229
|
+
cacheToolsList: server.cacheToolsList ?? false,
|
|
3230
|
+
...server.requireApproval != null ? { requireApproval: server.requireApproval } : {},
|
|
3231
|
+
...server.connectionRef ? { connectionRef: server.connectionRef } : {}
|
|
2681
3232
|
};
|
|
2682
3233
|
}
|
|
2683
3234
|
function mcpServerConfigFromMetadata(server) {
|
|
@@ -2685,7 +3236,9 @@ function mcpServerConfigFromMetadata(server) {
|
|
|
2685
3236
|
id: server.id,
|
|
2686
3237
|
...server.name ? { name: server.name } : {},
|
|
2687
3238
|
url: server.url,
|
|
2688
|
-
cacheToolsList: false
|
|
3239
|
+
cacheToolsList: false,
|
|
3240
|
+
requireApproval: server.requireApproval,
|
|
3241
|
+
...server.connectionRef ? { connectionRef: server.connectionRef } : {}
|
|
2689
3242
|
};
|
|
2690
3243
|
}
|
|
2691
3244
|
function settingsWithSessionMcpServerConfigs(settings, servers) {
|
|
@@ -2706,7 +3259,7 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
|
|
|
2706
3259
|
return { runtimeServers: [], dbServers: [], metadata: [] };
|
|
2707
3260
|
}
|
|
2708
3261
|
requirePermission(grant, "mcp_servers:attach");
|
|
2709
|
-
const encryptionKey = requireVariableSetEncryption(settings);
|
|
3262
|
+
const encryptionKey = servers.some((server) => Object.keys(server.headers ?? {}).length > 0) ? requireVariableSetEncryption(settings) : null;
|
|
2710
3263
|
const existingIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
2711
3264
|
const seenIds = /* @__PURE__ */ new Set();
|
|
2712
3265
|
const runtimeServers = [];
|
|
@@ -2736,6 +3289,7 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
|
|
|
2736
3289
|
timeoutMs: server.timeoutMs ?? null,
|
|
2737
3290
|
cacheToolsList: server.cacheToolsList ?? false,
|
|
2738
3291
|
requireApproval: server.requireApproval ?? null,
|
|
3292
|
+
connectionRef: server.connectionRef ?? null,
|
|
2739
3293
|
headersEncrypted
|
|
2740
3294
|
});
|
|
2741
3295
|
metadata.push({
|
|
@@ -2743,11 +3297,48 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
|
|
|
2743
3297
|
name: server.name ?? null,
|
|
2744
3298
|
url: server.url,
|
|
2745
3299
|
headerNames: Object.keys(headersEncrypted).sort(),
|
|
2746
|
-
credentialVersion: 1
|
|
3300
|
+
credentialVersion: 1,
|
|
3301
|
+
requireApproval: server.requireApproval ?? false,
|
|
3302
|
+
connectionRef: server.connectionRef ?? null
|
|
2747
3303
|
});
|
|
2748
3304
|
}
|
|
2749
3305
|
return { runtimeServers, dbServers, metadata };
|
|
2750
3306
|
}
|
|
3307
|
+
function validateInheritedSessionMcpServersForCreate(servers) {
|
|
3308
|
+
if (servers.length === 0) {
|
|
3309
|
+
return { runtimeServers: [], dbServers: [], metadata: [] };
|
|
3310
|
+
}
|
|
3311
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
3312
|
+
for (const server of servers) {
|
|
3313
|
+
if (seenIds.has(server.id)) {
|
|
3314
|
+
throw new HTTPException9(422, {
|
|
3315
|
+
message: `duplicate inherited session MCP server id: ${server.id}`
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
seenIds.add(server.id);
|
|
3319
|
+
if (reservedSessionMcpServerIds.has(server.id)) {
|
|
3320
|
+
throw new HTTPException9(422, {
|
|
3321
|
+
message: `reserved inherited session MCP server id: ${server.id}`
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
return {
|
|
3326
|
+
runtimeServers: servers.map(mcpServerConfigFromStoredInput),
|
|
3327
|
+
dbServers: servers.map((server) => ({
|
|
3328
|
+
...server,
|
|
3329
|
+
headersEncrypted: { ...server.headersEncrypted ?? {} }
|
|
3330
|
+
})),
|
|
3331
|
+
metadata: servers.map((server) => ({
|
|
3332
|
+
id: server.id,
|
|
3333
|
+
name: server.name ?? null,
|
|
3334
|
+
url: server.url,
|
|
3335
|
+
headerNames: Object.keys(server.headersEncrypted ?? {}).sort(),
|
|
3336
|
+
credentialVersion: 1,
|
|
3337
|
+
requireApproval: server.requireApproval ?? false,
|
|
3338
|
+
connectionRef: server.connectionRef ?? null
|
|
3339
|
+
}))
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
2751
3342
|
function validateSessionMcpCredentialUpdates(input) {
|
|
2752
3343
|
if (input.updates.length === 0) {
|
|
2753
3344
|
return [];
|
|
@@ -2792,18 +3383,27 @@ async function createAndStartSession(input) {
|
|
|
2792
3383
|
input.createIdempotencyKey
|
|
2793
3384
|
);
|
|
2794
3385
|
if (existing) {
|
|
3386
|
+
if (input.requestedSessionId && existing.id !== input.requestedSessionId) {
|
|
3387
|
+
throw new SessionIdConflictError(input.requestedSessionId);
|
|
3388
|
+
}
|
|
2795
3389
|
return await finishStartSession(
|
|
2796
3390
|
existing.temporalWorkflowId ? { ...input, seedTargetSandbox: null } : input,
|
|
2797
3391
|
existing
|
|
2798
3392
|
);
|
|
2799
3393
|
}
|
|
2800
3394
|
const { session: keyed, created } = await createSessionWithIdempotencyKey(input.db, {
|
|
3395
|
+
...input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {},
|
|
2801
3396
|
accountId: input.accountId,
|
|
2802
3397
|
workspaceId: input.workspaceId,
|
|
2803
3398
|
initialMessage: input.initialMessage,
|
|
3399
|
+
initialTurnInstructions: input.turnInstructions ?? null,
|
|
2804
3400
|
resources: input.resources,
|
|
2805
3401
|
tools: input.tools,
|
|
3402
|
+
...input.toolPolicy ? { toolPolicy: input.toolPolicy } : {},
|
|
2806
3403
|
metadata: sessionMetadata,
|
|
3404
|
+
...input.createdBy ? { createdBy: input.createdBy } : {},
|
|
3405
|
+
...input.createdByContext ? { createdByContext: input.createdByContext } : {},
|
|
3406
|
+
createdByActor: input.createdByActor ?? null,
|
|
2807
3407
|
model: input.model,
|
|
2808
3408
|
sandboxBackend: input.sandboxBackend,
|
|
2809
3409
|
variableSetId: input.variableSet?.id ?? null,
|
|
@@ -2826,12 +3426,18 @@ async function createAndStartSession(input) {
|
|
|
2826
3426
|
return await finishStartSession(input, keyed);
|
|
2827
3427
|
}
|
|
2828
3428
|
const session = await createSession(input.db, {
|
|
3429
|
+
...input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {},
|
|
2829
3430
|
accountId: input.accountId,
|
|
2830
3431
|
workspaceId: input.workspaceId,
|
|
2831
3432
|
initialMessage: input.initialMessage,
|
|
3433
|
+
initialTurnInstructions: input.turnInstructions ?? null,
|
|
2832
3434
|
resources: input.resources,
|
|
2833
3435
|
tools: input.tools,
|
|
3436
|
+
...input.toolPolicy ? { toolPolicy: input.toolPolicy } : {},
|
|
2834
3437
|
metadata: sessionMetadata,
|
|
3438
|
+
...input.createdBy ? { createdBy: input.createdBy } : {},
|
|
3439
|
+
...input.createdByContext ? { createdByContext: input.createdByContext } : {},
|
|
3440
|
+
createdByActor: input.createdByActor ?? null,
|
|
2835
3441
|
model: input.model,
|
|
2836
3442
|
sandboxBackend: input.sandboxBackend,
|
|
2837
3443
|
variableSetId: input.variableSet?.id ?? null,
|
|
@@ -2880,7 +3486,9 @@ async function finishStartSession(input, session) {
|
|
|
2880
3486
|
sessionId: session.id,
|
|
2881
3487
|
...input.clientEventId ? { clientEventId: input.clientEventId } : {},
|
|
2882
3488
|
reasoningEffortFallback: input.reasoningEffort,
|
|
3489
|
+
turnExecutionPolicy: input.turnExecutionPolicy,
|
|
2883
3490
|
createdEventPayload: {
|
|
3491
|
+
...input.toolPolicy ? { toolPolicy: input.toolPolicy } : {},
|
|
2884
3492
|
...input.variableSet ? { variableSetId: input.variableSet.id, variableSetName: input.variableSet.name } : {},
|
|
2885
3493
|
...input.sessionMcpServers?.length ? { mcpServers: input.sessionMcpServers } : {}
|
|
2886
3494
|
},
|
|
@@ -2900,36 +3508,49 @@ async function finishStartSession(input, session) {
|
|
|
2900
3508
|
wakeRevision: started.workflowWakeRevision
|
|
2901
3509
|
});
|
|
2902
3510
|
}
|
|
2903
|
-
|
|
3511
|
+
const persisted = await requireSession2(input.db, session.workspaceId, session.id);
|
|
3512
|
+
const initialTurnId = started.turn?.id ?? (await listSessionTurns(input.db, session.workspaceId, session.id, 1))[0]?.id ?? null;
|
|
3513
|
+
return { ...persisted, initialTurnId };
|
|
2904
3514
|
}
|
|
2905
3515
|
function workflowIdForSession(sessionId) {
|
|
2906
3516
|
return `session-${sessionId}`;
|
|
2907
3517
|
}
|
|
2908
|
-
function
|
|
3518
|
+
function canonicalConfiguredModel(settings, model) {
|
|
2909
3519
|
if (model === null || model === void 0) {
|
|
2910
|
-
return;
|
|
3520
|
+
return model;
|
|
2911
3521
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
3522
|
+
const canonicalModel = canonicalizeConfiguredModelId(settings, model);
|
|
3523
|
+
if (configuredAllowedModels(settings).includes(canonicalModel)) {
|
|
3524
|
+
return canonicalModel;
|
|
2914
3525
|
}
|
|
2915
|
-
if (settings.codexSubscriptionEnabled &&
|
|
2916
|
-
return;
|
|
3526
|
+
if (settings.codexSubscriptionEnabled && canonicalModel.startsWith(CODEX_MODEL_ID_PREFIX)) {
|
|
3527
|
+
return canonicalModel;
|
|
2917
3528
|
}
|
|
2918
3529
|
throw new HTTPException9(422, { message: `model is not available: ${model}` });
|
|
2919
3530
|
}
|
|
3531
|
+
function assertConfiguredModel(settings, model) {
|
|
3532
|
+
canonicalConfiguredModel(settings, model);
|
|
3533
|
+
}
|
|
2920
3534
|
async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model) {
|
|
2921
3535
|
if (model === null || model === void 0) {
|
|
2922
3536
|
return;
|
|
2923
3537
|
}
|
|
3538
|
+
const canonicalModel = canonicalConfiguredModel(settings, model);
|
|
3539
|
+
if (canonicalModel === null || canonicalModel === void 0) {
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
2924
3542
|
const policy = await getWorkspaceModelPolicy(db, workspaceId);
|
|
2925
3543
|
if (!policy) {
|
|
2926
3544
|
return;
|
|
2927
3545
|
}
|
|
2928
|
-
const providerId = policyProviderIdForModel(settings,
|
|
2929
|
-
const verdict = evaluateWorkspaceModelPolicy(policy, {
|
|
3546
|
+
const providerId = policyProviderIdForModel(settings, canonicalModel);
|
|
3547
|
+
const verdict = evaluateWorkspaceModelPolicy(policy, {
|
|
3548
|
+
providerId,
|
|
3549
|
+
modelId: canonicalModel
|
|
3550
|
+
});
|
|
2930
3551
|
if (!verdict.allowed) {
|
|
2931
3552
|
throw new HTTPException9(422, {
|
|
2932
|
-
message: verdict.reason === "provider" ? `model "${
|
|
3553
|
+
message: verdict.reason === "provider" ? `model "${canonicalModel}" is not allowed by this workspace's model policy: provider "${providerId}" is not in the allowed providers` : `model "${canonicalModel}" is not allowed by this workspace's model policy`
|
|
2933
3554
|
});
|
|
2934
3555
|
}
|
|
2935
3556
|
}
|
|
@@ -2950,7 +3571,7 @@ function reasoningEffortForSession(metadata, fallback) {
|
|
|
2950
3571
|
}
|
|
2951
3572
|
async function postUserMessageTurn(input) {
|
|
2952
3573
|
const { db, bus, workflowClient, settings, accountId, workspaceId, sessionId } = input;
|
|
2953
|
-
const requestedModel = input.model ?? null;
|
|
3574
|
+
const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
|
|
2954
3575
|
const requestedReasoningEffort = input.reasoningEffort ?? null;
|
|
2955
3576
|
assertConfiguredModel(settings, requestedModel);
|
|
2956
3577
|
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, requestedModel);
|
|
@@ -2967,17 +3588,24 @@ async function postUserMessageTurn(input) {
|
|
|
2967
3588
|
workspaceId,
|
|
2968
3589
|
sessionId,
|
|
2969
3590
|
subjectId: input.actor ?? accountId,
|
|
2970
|
-
|
|
3591
|
+
...input.actorLabel ? { subjectLabel: input.actorLabel } : {},
|
|
3592
|
+
actor: input.commandActor ?? {
|
|
3593
|
+
type: "human",
|
|
3594
|
+
subjectId: input.actor ?? accountId
|
|
3595
|
+
},
|
|
2971
3596
|
operationKey,
|
|
2972
3597
|
delivery: input.delivery ?? "send",
|
|
2973
3598
|
controlEtag: input.controlEtag ?? null,
|
|
2974
3599
|
expectedDraftRevision: input.expectedDraftRevision ?? null,
|
|
2975
3600
|
text: input.text,
|
|
3601
|
+
turnInstructions: input.turnInstructions ?? null,
|
|
2976
3602
|
resources: input.resources,
|
|
2977
3603
|
tools: input.tools,
|
|
3604
|
+
toolsProvided: input.toolsProvided,
|
|
2978
3605
|
model: requestedModel,
|
|
2979
3606
|
reasoningEffort: requestedReasoningEffort,
|
|
2980
|
-
reasoningEffortFallback: settings.openaiReasoningEffort,
|
|
3607
|
+
reasoningEffortFallback: input.reasoningEffortFallback ?? settings.openaiReasoningEffort,
|
|
3608
|
+
turnExecutionPolicy: input.turnExecutionPolicy,
|
|
2981
3609
|
source: input.origin === "operator" ? "api" : "user",
|
|
2982
3610
|
mcpCredentialUpdates: input.mcpCredentialUpdates ?? []
|
|
2983
3611
|
})
|
|
@@ -3044,24 +3672,79 @@ async function postUserMessageTurn(input) {
|
|
|
3044
3672
|
async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
3045
3673
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
3046
3674
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
3675
|
+
const parentSessionId = typeof grant.metadata?.["sessionId"] === "string" ? grant.metadata["sessionId"] : null;
|
|
3676
|
+
if (parentSessionId) {
|
|
3677
|
+
await requireSessionAuthorization(deps, grant, {
|
|
3678
|
+
sessionId: parentSessionId,
|
|
3679
|
+
operation: "session.child.create",
|
|
3680
|
+
surface: "core"
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3683
|
+
const parentSession = parentSessionId ? await getSession2(db, workspaceId, parentSessionId) : null;
|
|
3684
|
+
if (parentSessionId && !parentSession) {
|
|
3685
|
+
throw new HTTPException9(404, {
|
|
3686
|
+
message: `parent session not found in workspace: ${parentSessionId}`
|
|
3687
|
+
});
|
|
3688
|
+
}
|
|
3047
3689
|
const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
3048
3690
|
db,
|
|
3049
3691
|
workspaceId,
|
|
3050
3692
|
settings
|
|
3051
3693
|
);
|
|
3052
|
-
const sessionMcpServers = validateSessionMcpServersForCreate(
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
payload.mcpServers
|
|
3056
|
-
);
|
|
3694
|
+
const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers") ? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers) : parentSession ? validateInheritedSessionMcpServersForCreate(
|
|
3695
|
+
await listSessionMcpServersForChildInheritance(db, workspaceId, parentSession.id)
|
|
3696
|
+
) : validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers);
|
|
3057
3697
|
const runtimeSettings = settingsWithSessionMcpServerConfigs(
|
|
3058
3698
|
capabilityRuntimeSettings,
|
|
3059
3699
|
sessionMcpServers.runtimeServers
|
|
3060
3700
|
);
|
|
3061
|
-
const resources = normalizeResources(
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
const
|
|
3701
|
+
const resources = normalizeResources(
|
|
3702
|
+
hasOwnProperty(rawPayload, "resources") ? payload.resources : parentSession?.resources ?? payload.resources
|
|
3703
|
+
);
|
|
3704
|
+
const toolsProvided = hasOwnProperty(rawPayload, "tools");
|
|
3705
|
+
const requestedTools = validateToolRefs(
|
|
3706
|
+
toolsProvided ? payload.tools : parentSession?.tools ?? payload.tools,
|
|
3707
|
+
runtimeSettings
|
|
3708
|
+
);
|
|
3709
|
+
let selectedTools;
|
|
3710
|
+
let toolPolicy;
|
|
3711
|
+
if (parentSession) {
|
|
3712
|
+
const parentTracksWorkspaceDefaults = parentSession.toolPolicy?.mode === "workspace_default";
|
|
3713
|
+
const parentEffective = withFirstPartyTools(
|
|
3714
|
+
parentTracksWorkspaceDefaults ? withDefaultEnabledCapabilityMcpTools(
|
|
3715
|
+
availableToolRefs(parentSession.tools, runtimeSettings),
|
|
3716
|
+
settings,
|
|
3717
|
+
runtimeSettings
|
|
3718
|
+
) : parentSession.tools,
|
|
3719
|
+
runtimeSettings
|
|
3720
|
+
);
|
|
3721
|
+
if (toolsProvided) {
|
|
3722
|
+
assertToolRefsSubset(
|
|
3723
|
+
requestedTools,
|
|
3724
|
+
parentEffective,
|
|
3725
|
+
"child tools may only narrow the parent session tool policy"
|
|
3726
|
+
);
|
|
3727
|
+
selectedTools = requestedTools;
|
|
3728
|
+
toolPolicy = { mode: "explicit", inheritedFromSessionId: parentSession.id };
|
|
3729
|
+
} else {
|
|
3730
|
+
selectedTools = parentEffective;
|
|
3731
|
+
toolPolicy = {
|
|
3732
|
+
mode: parentTracksWorkspaceDefaults ? "workspace_default" : "inherited",
|
|
3733
|
+
inheritedFromSessionId: parentSession.id
|
|
3734
|
+
};
|
|
3735
|
+
}
|
|
3736
|
+
} else if (toolsProvided) {
|
|
3737
|
+
selectedTools = requestedTools;
|
|
3738
|
+
toolPolicy = { mode: "explicit", inheritedFromSessionId: null };
|
|
3739
|
+
} else {
|
|
3740
|
+
selectedTools = withDefaultEnabledCapabilityMcpTools(
|
|
3741
|
+
requestedTools,
|
|
3742
|
+
settings,
|
|
3743
|
+
capabilityRuntimeSettings
|
|
3744
|
+
);
|
|
3745
|
+
toolPolicy = { mode: "workspace_default", inheritedFromSessionId: null };
|
|
3746
|
+
}
|
|
3747
|
+
const tools = withFirstPartyTools(selectedTools, runtimeSettings);
|
|
3065
3748
|
await validateGitHubRepositorySelection(db, workspaceId, resources);
|
|
3066
3749
|
if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
3067
3750
|
throw new HTTPException9(503, { message: "object storage is not configured" });
|
|
@@ -3089,16 +3772,30 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
3089
3772
|
frozenRigVersionId = rig.activeVersion.id;
|
|
3090
3773
|
}
|
|
3091
3774
|
}
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
payload.model ?? settings.openaiModel
|
|
3098
|
-
);
|
|
3099
|
-
const model = payload.model ?? settings.openaiModel;
|
|
3775
|
+
const model = canonicalConfiguredModel(settings, payload.model ?? settings.openaiModel);
|
|
3776
|
+
if (model === null || model === void 0) {
|
|
3777
|
+
throw new Error("effective session model unexpectedly resolved to null");
|
|
3778
|
+
}
|
|
3779
|
+
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
|
|
3100
3780
|
const reasoningEffort = payload.reasoningEffort ?? settings.openaiReasoningEffort;
|
|
3101
|
-
|
|
3781
|
+
const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
3782
|
+
modelId: model,
|
|
3783
|
+
requestedModelId: payload.model ?? null,
|
|
3784
|
+
modelSource: payload.model === void 0 ? "deployment" : "explicit",
|
|
3785
|
+
reasoningEffort,
|
|
3786
|
+
reasoningSource: payload.reasoningEffort === void 0 ? "deployment" : "explicit"
|
|
3787
|
+
});
|
|
3788
|
+
const parentFirstPartyMcpPermissions = parentSession ? [...parentSession.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS] : null;
|
|
3789
|
+
if (parentFirstPartyMcpPermissions && payload.firstPartyMcpPermissions?.some(
|
|
3790
|
+
(permission) => !hasPermission(parentFirstPartyMcpPermissions, permission)
|
|
3791
|
+
)) {
|
|
3792
|
+
throw new HTTPException9(403, {
|
|
3793
|
+
message: "child first-party MCP permissions may only narrow the parent session grant"
|
|
3794
|
+
});
|
|
3795
|
+
}
|
|
3796
|
+
let firstPartyMcpPermissions = payload.firstPartyMcpPermissions ?? (parentFirstPartyMcpPermissions ? parentFirstPartyMcpPermissions.filter(
|
|
3797
|
+
(permission) => hasPermission(grant.permissions, permission)
|
|
3798
|
+
) : null);
|
|
3102
3799
|
if (firstPartyMcpPermissions && firstPartyMcpPermissions.length === 0) {
|
|
3103
3800
|
throw new HTTPException9(422, {
|
|
3104
3801
|
message: "firstPartyMcpPermissions must not be empty; omit it for the default worker permission set"
|
|
@@ -3112,9 +3809,10 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
3112
3809
|
}
|
|
3113
3810
|
}
|
|
3114
3811
|
if (payload.goal && firstPartyMcpPermissions && !firstPartyMcpPermissions.includes("goals:manage")) {
|
|
3115
|
-
|
|
3812
|
+
throw new HTTPException9(422, {
|
|
3813
|
+
message: "goal-bearing sessions require goals:manage in the resulting first-party MCP permission set"
|
|
3814
|
+
});
|
|
3116
3815
|
}
|
|
3117
|
-
const parentSessionId = typeof grant.metadata?.["sessionId"] === "string" ? grant.metadata["sessionId"] : null;
|
|
3118
3816
|
const sandboxChoice = payload.sandbox ?? (parentSessionId ? "shared" : "new");
|
|
3119
3817
|
let sandboxGroupId = null;
|
|
3120
3818
|
let inheritedBackend;
|
|
@@ -3127,12 +3825,10 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
3127
3825
|
message: "sandbox:'shared' requires a parent session (spawn from inside a session); use 'new' for a top-level create."
|
|
3128
3826
|
});
|
|
3129
3827
|
}
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
throw new HTTPException9(404, {
|
|
3133
|
-
message: `parent session not found in workspace: ${parentSessionId}`
|
|
3134
|
-
});
|
|
3828
|
+
if (!parentSession) {
|
|
3829
|
+
throw new Error("trusted parent session was not resolved");
|
|
3135
3830
|
}
|
|
3831
|
+
const parent = parentSession;
|
|
3136
3832
|
const parentBoxed = parent.sandboxBackend !== "none";
|
|
3137
3833
|
const variableSetMismatch = parentBoxed && !variableSetMatchesGroup(parent.variableSetId ?? null);
|
|
3138
3834
|
let rigMismatch = parentBoxed && !rigVersionMatchesGroup(parent.rigVersionId ?? null);
|
|
@@ -3218,50 +3914,71 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
3218
3914
|
quantity: 1,
|
|
3219
3915
|
model
|
|
3220
3916
|
});
|
|
3221
|
-
const
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3917
|
+
const creationInitiator = creationInitiatorForGrant(grant);
|
|
3918
|
+
let session;
|
|
3919
|
+
try {
|
|
3920
|
+
session = await createAndStartSession({
|
|
3921
|
+
...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
|
|
3922
|
+
db,
|
|
3923
|
+
bus,
|
|
3924
|
+
workflowClient,
|
|
3925
|
+
accountId: grant.accountId,
|
|
3926
|
+
workspaceId,
|
|
3927
|
+
initialMessage: payload.initialMessage,
|
|
3928
|
+
turnInstructions: payload.turnInstructions ?? null,
|
|
3929
|
+
resources,
|
|
3930
|
+
tools,
|
|
3931
|
+
toolPolicy,
|
|
3932
|
+
...payload.clientEventId ? { clientEventId: payload.clientEventId } : {},
|
|
3933
|
+
model,
|
|
3934
|
+
reasoningEffort,
|
|
3935
|
+
turnExecutionPolicy,
|
|
3936
|
+
// A shared spawn inherits the box's backend; a caller-supplied
|
|
3937
|
+
// sandboxBackend on a shared spawn is ignored (it is the same box). A
|
|
3938
|
+
// machine-targeted top-level create labels the home "selfhosted"
|
|
3939
|
+
// (machineHomeBackend), overriding the caller/deployment default so the row
|
|
3940
|
+
// matches where the session actually runs.
|
|
3941
|
+
sandboxBackend: inheritedBackend ?? machineHomeBackend ?? payload.sandboxBackend ?? settings.sandboxBackend,
|
|
3942
|
+
// Mirror the backend relabel on the OS axis: only a machine-targeted
|
|
3943
|
+
// top-level create carries a derived OS; everything else is omitted and the
|
|
3944
|
+
// "linux" default holds (shared spawns keep the parent-box behavior).
|
|
3945
|
+
...machineHomeOs ? { sandboxOs: machineHomeOs } : {},
|
|
3946
|
+
sandboxGroupId,
|
|
3947
|
+
metadata: payload.metadata,
|
|
3948
|
+
...creationInitiator.initiator ? { createdBy: creationInitiator.initiator } : {},
|
|
3949
|
+
...creationInitiator.context ? { createdByContext: creationInitiator.context } : {},
|
|
3950
|
+
createdByActor: creationInitiator.actor ?? null,
|
|
3951
|
+
variableSet: variableSet ? { id: variableSet.id, name: variableSet.name } : null,
|
|
3952
|
+
// Frozen rig binding (M3): both null for a rig-less session (today's path).
|
|
3953
|
+
rigId: frozenRigId,
|
|
3954
|
+
rigVersionId: frozenRigVersionId,
|
|
3955
|
+
goal: payload.goal ?? null,
|
|
3956
|
+
// Per-session persona instructions (already trimmed/validated by the
|
|
3957
|
+
// contracts schema). Persisted on the row; composed system-level at turn
|
|
3958
|
+
// time. Not surfaced as an event.
|
|
3959
|
+
instructions: payload.instructions ?? null,
|
|
3960
|
+
firstPartyMcpPermissions,
|
|
3961
|
+
mcpServers: sessionMcpServers.dbServers,
|
|
3962
|
+
sessionMcpServers: sessionMcpServers.metadata,
|
|
3963
|
+
parentSessionId,
|
|
3964
|
+
createIdempotencyKey: payload.idempotencyKey ?? null,
|
|
3965
|
+
// Create-time machine targeting (A-2a): when a target sandbox is named, the
|
|
3966
|
+
// active-sandbox pointer is seeded race-free inside createAndStartSession
|
|
3967
|
+
// (after the row exists, before the first turn dispatches). Validation
|
|
3968
|
+
// (ownership/liveness) lives in swapActiveSandbox; an invalid target 422s.
|
|
3969
|
+
seedTargetSandbox: payload.targetSandboxId ? { sandboxId: payload.targetSandboxId, settings, workingDir: payload.workingDir ?? null } : null
|
|
3970
|
+
});
|
|
3971
|
+
} catch (error) {
|
|
3972
|
+
if (error instanceof AgentCommandAuthorityError) {
|
|
3973
|
+
throw new HTTPException9(403, { message: error.message });
|
|
3974
|
+
}
|
|
3975
|
+
if (error instanceof SessionIdConflictError) {
|
|
3976
|
+
throw new HTTPException9(409, {
|
|
3977
|
+
message: "requested session id is already in use"
|
|
3978
|
+
});
|
|
3979
|
+
}
|
|
3980
|
+
throw error;
|
|
3981
|
+
}
|
|
3265
3982
|
await recordWorkspaceUsage(deps, {
|
|
3266
3983
|
accountId: grant.accountId,
|
|
3267
3984
|
workspaceId,
|
|
@@ -3271,31 +3988,76 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
3271
3988
|
unit: "run",
|
|
3272
3989
|
sourceResourceType: "session",
|
|
3273
3990
|
sourceResourceId: session.id,
|
|
3991
|
+
sessionId: session.id,
|
|
3992
|
+
initiator: session.createdBy,
|
|
3993
|
+
initiatorContext: session.createdByContext,
|
|
3994
|
+
origin: creationInitiator.actor ? "system" : "user",
|
|
3274
3995
|
idempotencyKey: `agent_run.created:${workspaceId}:${session.id}`
|
|
3275
3996
|
});
|
|
3276
3997
|
return session;
|
|
3277
3998
|
}
|
|
3278
3999
|
async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
|
|
4000
|
+
if (input.toolsProvided && !deps.settings.sessionTurnToolReplacementEnabled) {
|
|
4001
|
+
throw new HTTPException9(503, {
|
|
4002
|
+
message: "explicit follow-up tool replacement is temporarily unavailable until provenance-aware turn workers finish rolling out; omit tools to inherit the session policy and retry"
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
3279
4005
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
4006
|
+
await requireSessionAuthorization(deps, grant, {
|
|
4007
|
+
sessionId,
|
|
4008
|
+
operation: input.delivery === "steer" ? "session.steer" : "session.append",
|
|
4009
|
+
surface: "core"
|
|
4010
|
+
});
|
|
3280
4011
|
const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
3281
4012
|
db,
|
|
3282
4013
|
workspaceId,
|
|
3283
4014
|
settings
|
|
3284
4015
|
);
|
|
3285
4016
|
const existingSession = await requireSession2(db, workspaceId, sessionId);
|
|
4017
|
+
const requestedModel = canonicalConfiguredModel(settings, input.model ?? null) ?? null;
|
|
4018
|
+
const effectiveModel = canonicalConfiguredModel(settings, requestedModel ?? existingSession.model) ?? null;
|
|
4019
|
+
if (effectiveModel === null) {
|
|
4020
|
+
throw new Error("effective follow-up model unexpectedly resolved to null");
|
|
4021
|
+
}
|
|
4022
|
+
const sessionReasoningEffort = reasoningEffortForSession(
|
|
4023
|
+
existingSession.metadata,
|
|
4024
|
+
settings.openaiReasoningEffort
|
|
4025
|
+
);
|
|
4026
|
+
const effectiveReasoningEffort = input.reasoningEffort ?? sessionReasoningEffort;
|
|
4027
|
+
const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
|
|
4028
|
+
modelId: effectiveModel,
|
|
4029
|
+
requestedModelId: input.model ?? null,
|
|
4030
|
+
modelSource: input.model == null ? "session" : "explicit",
|
|
4031
|
+
reasoningEffort: effectiveReasoningEffort,
|
|
4032
|
+
reasoningSource: input.reasoningEffort == null ? "session" : "explicit"
|
|
4033
|
+
});
|
|
3286
4034
|
const runtimeSettings = settingsWithSessionMcpServerMetadata(
|
|
3287
4035
|
capabilityRuntimeSettings,
|
|
3288
4036
|
existingSession.mcpServers
|
|
3289
4037
|
);
|
|
3290
4038
|
const requestedResources = normalizeResources(input.resources ?? []);
|
|
3291
|
-
const
|
|
3292
|
-
const
|
|
4039
|
+
const tracksWorkspaceDefaults = existingSession.toolPolicy?.mode === "workspace_default";
|
|
4040
|
+
const sessionPolicyTools = withFirstPartyTools(
|
|
4041
|
+
tracksWorkspaceDefaults ? withDefaultEnabledCapabilityMcpTools(
|
|
4042
|
+
availableToolRefs(existingSession.tools, runtimeSettings),
|
|
4043
|
+
settings,
|
|
4044
|
+
capabilityRuntimeSettings
|
|
4045
|
+
) : existingSession.tools,
|
|
4046
|
+
runtimeSettings
|
|
4047
|
+
);
|
|
4048
|
+
const validatedTools = input.toolsProvided ? validateToolRefsForSessionPolicy({
|
|
4049
|
+
requested: input.tools ?? [],
|
|
4050
|
+
settings: runtimeSettings,
|
|
4051
|
+
allowedTools: sessionPolicyTools,
|
|
4052
|
+
message: "message tools may only narrow the session tool policy"
|
|
4053
|
+
}) : [];
|
|
4054
|
+
const requestedTools = input.toolsProvided ? validatedTools : [];
|
|
3293
4055
|
await requireLimit(deps, {
|
|
3294
4056
|
accountId: grant.accountId,
|
|
3295
4057
|
workspaceId,
|
|
3296
4058
|
action: "agent_run:create",
|
|
3297
4059
|
quantity: 1,
|
|
3298
|
-
model:
|
|
4060
|
+
model: effectiveModel
|
|
3299
4061
|
});
|
|
3300
4062
|
if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
3301
4063
|
throw new HTTPException9(503, { message: "object storage is not configured" });
|
|
@@ -3311,6 +4073,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
|
|
|
3311
4073
|
session: existingSession,
|
|
3312
4074
|
updates: input.mcpCredentialUpdates ?? []
|
|
3313
4075
|
});
|
|
4076
|
+
const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
|
|
3314
4077
|
const { accepted, turn } = await postUserMessageTurn({
|
|
3315
4078
|
db,
|
|
3316
4079
|
bus,
|
|
@@ -3320,14 +4083,27 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
|
|
|
3320
4083
|
workspaceId,
|
|
3321
4084
|
sessionId,
|
|
3322
4085
|
text: input.text,
|
|
4086
|
+
turnInstructions: input.turnInstructions ?? null,
|
|
3323
4087
|
resources: requestedResources,
|
|
3324
4088
|
tools: requestedTools,
|
|
4089
|
+
toolsProvided: input.toolsProvided,
|
|
3325
4090
|
model: input.model ?? null,
|
|
3326
4091
|
reasoningEffort: input.reasoningEffort ?? null,
|
|
4092
|
+
reasoningEffortFallback: sessionReasoningEffort,
|
|
4093
|
+
turnExecutionPolicy,
|
|
3327
4094
|
mcpCredentialUpdates,
|
|
3328
4095
|
delivery: input.delivery ?? "send",
|
|
3329
|
-
origin: input.origin ?? "human",
|
|
4096
|
+
origin: delegatedServiceInitiator ? "operator" : input.origin ?? "human",
|
|
3330
4097
|
actor: grant.subjectId,
|
|
4098
|
+
...grant.subjectLabel ? { actorLabel: grant.subjectLabel } : {},
|
|
4099
|
+
...delegatedServiceInitiator ? {
|
|
4100
|
+
commandActor: {
|
|
4101
|
+
type: "service",
|
|
4102
|
+
subjectId: delegatedServiceInitiator.initiator.subjectId,
|
|
4103
|
+
...delegatedServiceInitiator.initiator.label ? { subjectLabel: delegatedServiceInitiator.initiator.label } : {},
|
|
4104
|
+
context: delegatedServiceInitiator.context
|
|
4105
|
+
}
|
|
4106
|
+
} : {},
|
|
3331
4107
|
...input.controlEtag !== void 0 ? { controlEtag: input.controlEtag } : {},
|
|
3332
4108
|
...input.expectedDraftRevision !== void 0 ? { expectedDraftRevision: input.expectedDraftRevision } : {},
|
|
3333
4109
|
...input.clientEventId ? { clientEventId: input.clientEventId } : {}
|
|
@@ -3341,12 +4117,23 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
|
|
|
3341
4117
|
unit: "run",
|
|
3342
4118
|
sourceResourceType: "session_turn",
|
|
3343
4119
|
sourceResourceId: turn.id,
|
|
4120
|
+
sessionId,
|
|
4121
|
+
turnId: turn.id,
|
|
4122
|
+
initiator: turn.initiator,
|
|
4123
|
+
initiatorContext: turn.initiatorContext,
|
|
4124
|
+
origin: turn.source,
|
|
3344
4125
|
idempotencyKey: `agent_run.created:${workspaceId}:${turn.id}`
|
|
3345
4126
|
});
|
|
3346
4127
|
return { accepted, turn };
|
|
3347
4128
|
}
|
|
3348
|
-
async function updateSessionTitle(deps,
|
|
4129
|
+
async function updateSessionTitle(deps, grant, sessionId, title, source) {
|
|
3349
4130
|
const { db, bus } = deps;
|
|
4131
|
+
const authorization = await requireSessionAuthorization(deps, grant, {
|
|
4132
|
+
sessionId,
|
|
4133
|
+
operation: "session.title.write",
|
|
4134
|
+
surface: "core"
|
|
4135
|
+
});
|
|
4136
|
+
const workspaceId = grant.workspaceId;
|
|
3350
4137
|
const result = await updateSessionTitleRow(db, { workspaceId, sessionId, title, source });
|
|
3351
4138
|
if (result.updated) {
|
|
3352
4139
|
await appendAndPublishEvents(db, bus, workspaceId, sessionId, [
|
|
@@ -3359,10 +4146,67 @@ async function updateSessionTitle(deps, workspaceId, sessionId, title, source) {
|
|
|
3359
4146
|
}
|
|
3360
4147
|
]);
|
|
3361
4148
|
}
|
|
3362
|
-
return
|
|
4149
|
+
return {
|
|
4150
|
+
...result,
|
|
4151
|
+
relatedSessionAccess: authorization?.relatedSessionAccess ?? "root"
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
async function updateSessionMcpApprovalPolicy(deps, grant, sessionId, serverId, requireApproval) {
|
|
4155
|
+
const normalizedPolicy = SessionMcpApprovalPolicy.parse(requireApproval);
|
|
4156
|
+
await requireSessionAuthorization(deps, grant, {
|
|
4157
|
+
sessionId,
|
|
4158
|
+
operation: "session.mcp.approval_policy.write",
|
|
4159
|
+
surface: "core"
|
|
4160
|
+
});
|
|
4161
|
+
requirePermission(grant, "sessions:control");
|
|
4162
|
+
const outcome = {};
|
|
4163
|
+
const events = await appendSessionEventsWithLockedSessionUpdate(
|
|
4164
|
+
deps.db,
|
|
4165
|
+
grant.workspaceId,
|
|
4166
|
+
sessionId,
|
|
4167
|
+
async (_session, context) => {
|
|
4168
|
+
const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
|
|
4169
|
+
if (!result.server) {
|
|
4170
|
+
throw new HTTPException9(404, { message: "session MCP server not found" });
|
|
4171
|
+
}
|
|
4172
|
+
outcome.server = result.server;
|
|
4173
|
+
return {
|
|
4174
|
+
events: result.changed ? [
|
|
4175
|
+
{
|
|
4176
|
+
type: "session.mcp.approval_policy.updated",
|
|
4177
|
+
payload: {
|
|
4178
|
+
serverId,
|
|
4179
|
+
effectiveFrom: "next_attempt"
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
] : []
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
);
|
|
4186
|
+
const updatedServer = outcome.server;
|
|
4187
|
+
if (!updatedServer) {
|
|
4188
|
+
throw new Error("session MCP approval policy update returned no server");
|
|
4189
|
+
}
|
|
4190
|
+
await publishDurableSessionEvents(deps.bus, grant.workspaceId, sessionId, events);
|
|
4191
|
+
return {
|
|
4192
|
+
server: updatedServer,
|
|
4193
|
+
effectiveFrom: "next_attempt"
|
|
4194
|
+
};
|
|
3363
4195
|
}
|
|
3364
|
-
async function readSessionLineage(
|
|
3365
|
-
const
|
|
4196
|
+
async function readSessionLineage(deps, grant, sessionId) {
|
|
4197
|
+
const authorization = await requireSessionAuthorization(deps, grant, {
|
|
4198
|
+
sessionId,
|
|
4199
|
+
operation: "session.lineage.read",
|
|
4200
|
+
surface: "core"
|
|
4201
|
+
});
|
|
4202
|
+
if (authorization?.relatedSessionAccess === "target") {
|
|
4203
|
+
const session = await getSession2(deps.db, grant.workspaceId, sessionId);
|
|
4204
|
+
if (!session) {
|
|
4205
|
+
throw new HTTPException9(404, { message: "session not found" });
|
|
4206
|
+
}
|
|
4207
|
+
return { ancestors: [], children: [], truncated: false };
|
|
4208
|
+
}
|
|
4209
|
+
const lineage = await getSessionLineage(deps.db, grant.workspaceId, sessionId);
|
|
3366
4210
|
if (!lineage) {
|
|
3367
4211
|
throw new HTTPException9(404, { message: "session not found" });
|
|
3368
4212
|
}
|
|
@@ -3552,13 +4396,8 @@ function manualScheduledTaskTriggerUsageKey(workspaceId, taskId, triggerToken) {
|
|
|
3552
4396
|
return `agent_run.created:scheduled-trigger:${workspaceId}:${taskId}:${triggerToken}`;
|
|
3553
4397
|
}
|
|
3554
4398
|
async function validateScheduledTaskAgentConfig(input) {
|
|
3555
|
-
|
|
3556
|
-
await assertWorkspaceModelPolicyAllows(
|
|
3557
|
-
input.db,
|
|
3558
|
-
input.settings,
|
|
3559
|
-
input.workspaceId,
|
|
3560
|
-
input.payload.agentConfig.model
|
|
3561
|
-
);
|
|
4399
|
+
const model = canonicalConfiguredModel(input.settings, input.payload.agentConfig.model);
|
|
4400
|
+
await assertWorkspaceModelPolicyAllows(input.db, input.settings, input.workspaceId, model);
|
|
3562
4401
|
const resources = normalizeResources(input.payload.agentConfig.resources ?? []);
|
|
3563
4402
|
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
3564
4403
|
input.db,
|
|
@@ -3578,6 +4417,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
3578
4417
|
await validateFileResources(input.db, input.workspaceId, resources);
|
|
3579
4418
|
return {
|
|
3580
4419
|
...input.payload.agentConfig,
|
|
4420
|
+
...model === void 0 || model === null ? {} : { model },
|
|
3581
4421
|
prompt,
|
|
3582
4422
|
resources,
|
|
3583
4423
|
tools
|
|
@@ -3651,13 +4491,15 @@ import {
|
|
|
3651
4491
|
deleteSessionQueueItemInTransaction,
|
|
3652
4492
|
editQueuedTurnInTransaction,
|
|
3653
4493
|
getComposerDraftInTransaction,
|
|
3654
|
-
getSession as
|
|
4494
|
+
getSession as getSession3,
|
|
3655
4495
|
getSessionEvent as getSessionEvent2,
|
|
3656
4496
|
getWorkspaceControlEvent as getWorkspaceControlEvent2,
|
|
3657
4497
|
getSessionQueueSnapshot,
|
|
3658
4498
|
moveQueuedTurnInTransaction,
|
|
3659
4499
|
mutateSessionControlInTransaction,
|
|
3660
4500
|
mutateWorkspaceControlInTransaction,
|
|
4501
|
+
projectEffectiveControlForRelatedAccess,
|
|
4502
|
+
runIdempotentPersistenceTransaction,
|
|
3661
4503
|
saveComposerDraftInTransaction,
|
|
3662
4504
|
sendAgentMessageInTransaction,
|
|
3663
4505
|
serializeEffectiveSessionControl,
|
|
@@ -3670,6 +4512,42 @@ import {
|
|
|
3670
4512
|
publishDurableSessionEvents as publishDurableSessionEvents2,
|
|
3671
4513
|
publishDurableWorkspaceControlEvent as publishDurableWorkspaceControlEvent2
|
|
3672
4514
|
} from "@opengeni/events";
|
|
4515
|
+
function humanAccessGrant(context) {
|
|
4516
|
+
return {
|
|
4517
|
+
accountId: context.accountId,
|
|
4518
|
+
workspaceId: context.workspaceId,
|
|
4519
|
+
subjectId: context.subjectId,
|
|
4520
|
+
permissions: []
|
|
4521
|
+
};
|
|
4522
|
+
}
|
|
4523
|
+
function agentAccessGrant(context) {
|
|
4524
|
+
return {
|
|
4525
|
+
accountId: context.accountId,
|
|
4526
|
+
workspaceId: context.workspaceId,
|
|
4527
|
+
subjectId: context.subjectId,
|
|
4528
|
+
permissions: [],
|
|
4529
|
+
metadata: {
|
|
4530
|
+
sessionId: context.callerSessionId,
|
|
4531
|
+
turnId: context.callerTurnId,
|
|
4532
|
+
attemptId: context.callerAttemptId,
|
|
4533
|
+
executionGeneration: context.callerExecutionGeneration
|
|
4534
|
+
}
|
|
4535
|
+
};
|
|
4536
|
+
}
|
|
4537
|
+
async function authorizeHumanSessionCommand(deps, context, operation) {
|
|
4538
|
+
return await requireSessionAuthorization(deps, humanAccessGrant(context), {
|
|
4539
|
+
sessionId: context.sessionId,
|
|
4540
|
+
operation,
|
|
4541
|
+
surface: "core"
|
|
4542
|
+
});
|
|
4543
|
+
}
|
|
4544
|
+
async function authorizeAgentSessionCommand(deps, context, targetSessionId, operation) {
|
|
4545
|
+
return await requireSessionAuthorization(deps, agentAccessGrant(context), {
|
|
4546
|
+
sessionId: targetSessionId,
|
|
4547
|
+
operation,
|
|
4548
|
+
surface: "core"
|
|
4549
|
+
});
|
|
4550
|
+
}
|
|
3673
4551
|
function agentActor(context) {
|
|
3674
4552
|
return {
|
|
3675
4553
|
type: "agent_attempt",
|
|
@@ -3679,6 +4557,20 @@ function agentActor(context) {
|
|
|
3679
4557
|
executionGeneration: context.callerExecutionGeneration
|
|
3680
4558
|
};
|
|
3681
4559
|
}
|
|
4560
|
+
async function runAgentCommandPersistenceTransaction(deps, context, input) {
|
|
4561
|
+
return await runIdempotentPersistenceTransaction(
|
|
4562
|
+
{
|
|
4563
|
+
stage: input.stage,
|
|
4564
|
+
eventTypes: input.eventTypes,
|
|
4565
|
+
maxAttempts: 3
|
|
4566
|
+
},
|
|
4567
|
+
async () => await withWorkspaceRls(
|
|
4568
|
+
deps.db,
|
|
4569
|
+
context.workspaceId,
|
|
4570
|
+
async (scoped) => scoped.transaction(async (tx) => await input.transaction(tx))
|
|
4571
|
+
)
|
|
4572
|
+
);
|
|
4573
|
+
}
|
|
3682
4574
|
async function publishAndWakeAgentCommand(deps, input) {
|
|
3683
4575
|
await publishSessionEventIds(deps, input.workspaceId, input.sessionId, input.eventIds);
|
|
3684
4576
|
if (!input.shouldSignal || input.wakeRevision === null) return;
|
|
@@ -3730,20 +4622,19 @@ async function publishWorkspaceControlEvent(deps, workspaceId, eventId) {
|
|
|
3730
4622
|
await publishDurableWorkspaceControlEvent2(deps.bus, workspaceId, event);
|
|
3731
4623
|
}
|
|
3732
4624
|
async function sendAgentSessionMessage(deps, context, input) {
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
);
|
|
4625
|
+
await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.append");
|
|
4626
|
+
const result = await runAgentCommandPersistenceTransaction(deps, context, {
|
|
4627
|
+
stage: "session_commands.agent_message",
|
|
4628
|
+
eventTypes: ["system.update.pending"],
|
|
4629
|
+
transaction: async (tx) => await sendAgentMessageInTransaction(tx, {
|
|
4630
|
+
accountId: context.accountId,
|
|
4631
|
+
workspaceId: context.workspaceId,
|
|
4632
|
+
targetSessionId: input.targetSessionId,
|
|
4633
|
+
actor: agentActor(context),
|
|
4634
|
+
operationKey: input.idempotencyKey,
|
|
4635
|
+
text: input.text
|
|
4636
|
+
})
|
|
4637
|
+
});
|
|
3747
4638
|
await publishAndWakeAgentCommand(deps, {
|
|
3748
4639
|
accountId: context.accountId,
|
|
3749
4640
|
workspaceId: context.workspaceId,
|
|
@@ -3758,20 +4649,19 @@ async function sendAgentSessionMessage(deps, context, input) {
|
|
|
3758
4649
|
return result;
|
|
3759
4650
|
}
|
|
3760
4651
|
async function steerAgentSession(deps, context, input) {
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
);
|
|
4652
|
+
await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.steer");
|
|
4653
|
+
const result = await runAgentCommandPersistenceTransaction(deps, context, {
|
|
4654
|
+
stage: "session_commands.agent_steer",
|
|
4655
|
+
eventTypes: ["session.control.steer_requested", "system.update.pending", "turn.superseded"],
|
|
4656
|
+
transaction: async (tx) => await steerAgentSessionInTransaction(tx, {
|
|
4657
|
+
accountId: context.accountId,
|
|
4658
|
+
workspaceId: context.workspaceId,
|
|
4659
|
+
targetSessionId: input.targetSessionId,
|
|
4660
|
+
actor: agentActor(context),
|
|
4661
|
+
operationKey: input.idempotencyKey,
|
|
4662
|
+
instruction: input.instruction
|
|
4663
|
+
})
|
|
4664
|
+
});
|
|
3775
4665
|
await publishAndWakeAgentCommand(deps, {
|
|
3776
4666
|
accountId: context.accountId,
|
|
3777
4667
|
workspaceId: context.workspaceId,
|
|
@@ -3786,6 +4676,7 @@ async function steerAgentSession(deps, context, input) {
|
|
|
3786
4676
|
return result;
|
|
3787
4677
|
}
|
|
3788
4678
|
async function controlAgentSessionWorkstream(deps, context, input) {
|
|
4679
|
+
await authorizeAgentSessionCommand(deps, context, input.targetSessionId, "session.control");
|
|
3789
4680
|
const result = await withWorkspaceRls(
|
|
3790
4681
|
deps.db,
|
|
3791
4682
|
context.workspaceId,
|
|
@@ -3829,6 +4720,7 @@ function composerDraft(row) {
|
|
|
3829
4720
|
text: row.text,
|
|
3830
4721
|
resources: row.resources,
|
|
3831
4722
|
tools: row.tools,
|
|
4723
|
+
toolsProvided: row.toolsProvided,
|
|
3832
4724
|
model: row.model,
|
|
3833
4725
|
reasoningEffort: row.reasoningEffort,
|
|
3834
4726
|
sourceTurnId: row.sourceTurnId,
|
|
@@ -3836,12 +4728,20 @@ function composerDraft(row) {
|
|
|
3836
4728
|
updatedAt: row.updatedAt.toISOString()
|
|
3837
4729
|
};
|
|
3838
4730
|
}
|
|
3839
|
-
async function authoritativeQueue(db, workspaceId, sessionId) {
|
|
4731
|
+
async function authoritativeQueue(db, workspaceId, sessionId, relatedSessionAccess) {
|
|
3840
4732
|
const snapshot = await getSessionQueueSnapshot(db, workspaceId, sessionId);
|
|
3841
4733
|
if (!snapshot) throw new Error(`Session not found: ${sessionId}`);
|
|
3842
|
-
return
|
|
4734
|
+
return {
|
|
4735
|
+
...snapshot,
|
|
4736
|
+
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
4737
|
+
snapshot.effectiveControl,
|
|
4738
|
+
sessionId,
|
|
4739
|
+
relatedSessionAccess
|
|
4740
|
+
)
|
|
4741
|
+
};
|
|
3843
4742
|
}
|
|
3844
4743
|
async function moveHumanQueuePrompt(deps, context, turnId, input) {
|
|
4744
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
3845
4745
|
const result = await withWorkspaceRls(
|
|
3846
4746
|
deps.db,
|
|
3847
4747
|
context.workspaceId,
|
|
@@ -3858,12 +4758,18 @@ async function moveHumanQueuePrompt(deps, context, turnId, input) {
|
|
|
3858
4758
|
);
|
|
3859
4759
|
const response = {
|
|
3860
4760
|
receipt: receipt(result.receipt),
|
|
3861
|
-
snapshot: await authoritativeQueue(
|
|
4761
|
+
snapshot: await authoritativeQueue(
|
|
4762
|
+
deps.db,
|
|
4763
|
+
context.workspaceId,
|
|
4764
|
+
context.sessionId,
|
|
4765
|
+
authorization?.relatedSessionAccess ?? "root"
|
|
4766
|
+
)
|
|
3862
4767
|
};
|
|
3863
4768
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
3864
4769
|
return response;
|
|
3865
4770
|
}
|
|
3866
4771
|
async function deleteHumanQueuePrompt(deps, context, turnId, input) {
|
|
4772
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
3867
4773
|
const result = await withWorkspaceRls(
|
|
3868
4774
|
deps.db,
|
|
3869
4775
|
context.workspaceId,
|
|
@@ -3880,12 +4786,18 @@ async function deleteHumanQueuePrompt(deps, context, turnId, input) {
|
|
|
3880
4786
|
);
|
|
3881
4787
|
const response = {
|
|
3882
4788
|
receipt: receipt(result.receipt),
|
|
3883
|
-
snapshot: await authoritativeQueue(
|
|
4789
|
+
snapshot: await authoritativeQueue(
|
|
4790
|
+
deps.db,
|
|
4791
|
+
context.workspaceId,
|
|
4792
|
+
context.sessionId,
|
|
4793
|
+
authorization?.relatedSessionAccess ?? "root"
|
|
4794
|
+
)
|
|
3884
4795
|
};
|
|
3885
4796
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
3886
4797
|
return response;
|
|
3887
4798
|
}
|
|
3888
4799
|
async function editHumanQueuePrompt(deps, context, turnId, input) {
|
|
4800
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
3889
4801
|
const result = await withWorkspaceSubjectRls2(
|
|
3890
4802
|
deps.db,
|
|
3891
4803
|
context.workspaceId,
|
|
@@ -3904,13 +4816,19 @@ async function editHumanQueuePrompt(deps, context, turnId, input) {
|
|
|
3904
4816
|
);
|
|
3905
4817
|
const response = {
|
|
3906
4818
|
receipt: receipt(result.receipt),
|
|
3907
|
-
snapshot: await authoritativeQueue(
|
|
4819
|
+
snapshot: await authoritativeQueue(
|
|
4820
|
+
deps.db,
|
|
4821
|
+
context.workspaceId,
|
|
4822
|
+
context.sessionId,
|
|
4823
|
+
authorization?.relatedSessionAccess ?? "root"
|
|
4824
|
+
),
|
|
3908
4825
|
draft: composerDraft(result.draft)
|
|
3909
4826
|
};
|
|
3910
4827
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
3911
4828
|
return response;
|
|
3912
4829
|
}
|
|
3913
4830
|
async function steerHumanQueuePrompt(deps, context, turnId, input) {
|
|
4831
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.queue.control");
|
|
3914
4832
|
const result = await withWorkspaceRls(
|
|
3915
4833
|
deps.db,
|
|
3916
4834
|
context.workspaceId,
|
|
@@ -3927,13 +4845,19 @@ async function steerHumanQueuePrompt(deps, context, turnId, input) {
|
|
|
3927
4845
|
);
|
|
3928
4846
|
const response = {
|
|
3929
4847
|
receipt: receipt(result.receipt),
|
|
3930
|
-
snapshot: await authoritativeQueue(
|
|
4848
|
+
snapshot: await authoritativeQueue(
|
|
4849
|
+
deps.db,
|
|
4850
|
+
context.workspaceId,
|
|
4851
|
+
context.sessionId,
|
|
4852
|
+
authorization?.relatedSessionAccess ?? "root"
|
|
4853
|
+
)
|
|
3931
4854
|
};
|
|
3932
4855
|
await publishSessionEventIds(deps, context.workspaceId, context.sessionId, result.eventIds);
|
|
3933
4856
|
await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
|
|
3934
4857
|
return response;
|
|
3935
4858
|
}
|
|
3936
4859
|
async function controlHumanSessionWorkstream(deps, context, input) {
|
|
4860
|
+
const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
|
|
3937
4861
|
const result = await withWorkspaceRls(
|
|
3938
4862
|
deps.db,
|
|
3939
4863
|
context.workspaceId,
|
|
@@ -3952,7 +4876,11 @@ async function controlHumanSessionWorkstream(deps, context, input) {
|
|
|
3952
4876
|
);
|
|
3953
4877
|
const response = {
|
|
3954
4878
|
receipt: receipt(result.receipt),
|
|
3955
|
-
effectiveControl:
|
|
4879
|
+
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
4880
|
+
serializeEffectiveSessionControl(result.control),
|
|
4881
|
+
context.sessionId,
|
|
4882
|
+
authorization?.relatedSessionAccess ?? "root"
|
|
4883
|
+
),
|
|
3956
4884
|
interruptionCount: result.interruptionCount,
|
|
3957
4885
|
wakeCount: result.wakeCount
|
|
3958
4886
|
};
|
|
@@ -3990,9 +4918,10 @@ async function controlHumanWorkspace(deps, context, input) {
|
|
|
3990
4918
|
await requestControlWakeDispatch(deps, result.wakeCount);
|
|
3991
4919
|
return response;
|
|
3992
4920
|
}
|
|
3993
|
-
async function getHumanComposerDraft(
|
|
4921
|
+
async function getHumanComposerDraft(deps, context) {
|
|
4922
|
+
await authorizeHumanSessionCommand(deps, context, "session.composer.read");
|
|
3994
4923
|
const row = await withWorkspaceSubjectRls2(
|
|
3995
|
-
db,
|
|
4924
|
+
deps.db,
|
|
3996
4925
|
context.workspaceId,
|
|
3997
4926
|
context.subjectId,
|
|
3998
4927
|
(scoped) => getComposerDraftInTransaction(scoped, {
|
|
@@ -4003,13 +4932,14 @@ async function getHumanComposerDraft(db, context) {
|
|
|
4003
4932
|
);
|
|
4004
4933
|
const mapped = composerDraft(row);
|
|
4005
4934
|
if (mapped) return mapped;
|
|
4006
|
-
const session = await
|
|
4935
|
+
const session = await getSession3(deps.db, context.workspaceId, context.sessionId);
|
|
4007
4936
|
if (!session) throw new Error(`Session not found: ${context.sessionId}`);
|
|
4008
4937
|
return {
|
|
4009
4938
|
revision: 0,
|
|
4010
4939
|
text: "",
|
|
4011
4940
|
resources: [],
|
|
4012
4941
|
tools: [],
|
|
4942
|
+
toolsProvided: false,
|
|
4013
4943
|
model: session.model,
|
|
4014
4944
|
reasoningEffort: reasoningEffortForMetadata2(session.metadata, "medium"),
|
|
4015
4945
|
sourceTurnId: null,
|
|
@@ -4017,9 +4947,10 @@ async function getHumanComposerDraft(db, context) {
|
|
|
4017
4947
|
updatedAt: null
|
|
4018
4948
|
};
|
|
4019
4949
|
}
|
|
4020
|
-
async function saveHumanComposerDraft(
|
|
4950
|
+
async function saveHumanComposerDraft(deps, context, input) {
|
|
4951
|
+
await authorizeHumanSessionCommand(deps, context, "session.composer.write");
|
|
4021
4952
|
const row = await withWorkspaceSubjectRls2(
|
|
4022
|
-
db,
|
|
4953
|
+
deps.db,
|
|
4023
4954
|
context.workspaceId,
|
|
4024
4955
|
context.subjectId,
|
|
4025
4956
|
(scoped) => scoped.transaction(
|
|
@@ -4040,9 +4971,12 @@ export {
|
|
|
4040
4971
|
MAX_ENVIRONMENTS_PER_WORKSPACE,
|
|
4041
4972
|
MAX_RIGS_PER_WORKSPACE,
|
|
4042
4973
|
MAX_VARIABLES_PER_ENVIRONMENT,
|
|
4974
|
+
SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
|
|
4043
4975
|
SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS,
|
|
4044
4976
|
SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID,
|
|
4045
4977
|
SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE,
|
|
4978
|
+
SessionAuthorizationDeniedError,
|
|
4979
|
+
SessionAuthorizationUnavailableError,
|
|
4046
4980
|
acceptSessionUserMessage,
|
|
4047
4981
|
activateRigVersionForApi,
|
|
4048
4982
|
appendRigSetupCommand,
|
|
@@ -4051,12 +4985,15 @@ export {
|
|
|
4051
4985
|
assertAllowedVariableSetVariableName,
|
|
4052
4986
|
assertConfiguredModel,
|
|
4053
4987
|
assertPackSandboxImageCompatible,
|
|
4988
|
+
assertToolRefsSubset,
|
|
4054
4989
|
assertWorkspaceDeletable,
|
|
4055
4990
|
assertWorkspaceMemberRemovable,
|
|
4056
4991
|
assertWorkspaceModelPolicyAllows,
|
|
4992
|
+
availableToolRefs,
|
|
4057
4993
|
buildCapabilityCatalog,
|
|
4058
4994
|
buildFleetContextForSession,
|
|
4059
4995
|
buildMarketingDailyAnalysisAgentConfig,
|
|
4996
|
+
canonicalConfiguredModel,
|
|
4060
4997
|
checkLimit,
|
|
4061
4998
|
classifyRigVerificationOutcome,
|
|
4062
4999
|
controlAgentSessionWorkstream,
|
|
@@ -4114,10 +5051,13 @@ export {
|
|
|
4114
5051
|
requireRigChangeForApi,
|
|
4115
5052
|
requireRigForApi,
|
|
4116
5053
|
requireScheduledTaskForApi,
|
|
5054
|
+
requireSessionAuthorization,
|
|
5055
|
+
requireSessionAuthorizationListScope,
|
|
4117
5056
|
requireVariableSetEncryption,
|
|
4118
5057
|
requireVariableSetForApi,
|
|
4119
5058
|
resolveCapabilityPack,
|
|
4120
5059
|
resolveMemberSubjectId,
|
|
5060
|
+
resolveSessionToolPolicy,
|
|
4121
5061
|
restoreScheduledTask,
|
|
4122
5062
|
rigActorForGrant,
|
|
4123
5063
|
routingEnabled,
|
|
@@ -4127,6 +5067,7 @@ export {
|
|
|
4127
5067
|
scheduledTaskToolsProvided,
|
|
4128
5068
|
scheduledTaskTriggerToken,
|
|
4129
5069
|
sendAgentSessionMessage,
|
|
5070
|
+
sessionWithEffectiveToolPolicy,
|
|
4130
5071
|
settingsWithEnabledCapabilityMcpServers,
|
|
4131
5072
|
settingsWithMcpCapabilityServers,
|
|
4132
5073
|
settingsWithSessionMcpServerMetadata,
|
|
@@ -4137,16 +5078,21 @@ export {
|
|
|
4137
5078
|
syncCreatedScheduledTask,
|
|
4138
5079
|
syncUpdatedScheduledTask,
|
|
4139
5080
|
updateRigForApi,
|
|
5081
|
+
updateSessionMcpApprovalPolicy,
|
|
4140
5082
|
updateSessionTitle,
|
|
4141
5083
|
validateFileResources,
|
|
4142
5084
|
validateGitHubRepositorySelection,
|
|
4143
5085
|
validateGitHubRepositorySelectionShape,
|
|
5086
|
+
validateGitHubRepositorySelectionShapes,
|
|
4144
5087
|
validateMcpCapabilityConnection,
|
|
4145
5088
|
validateToolRefs,
|
|
5089
|
+
validateToolRefsForSessionPolicy,
|
|
4146
5090
|
validateVariableSetAttachment,
|
|
4147
5091
|
validatedScheduledTaskUpdate,
|
|
4148
5092
|
withDefaultEnabledCapabilityMcpTools,
|
|
4149
5093
|
workflowIdForSession,
|
|
5094
|
+
workspaceSessionToolPolicyDefaultServerIds,
|
|
5095
|
+
workspaceSessionToolPolicyServerIds,
|
|
4150
5096
|
wrapChannelABoxWithRouting
|
|
4151
5097
|
};
|
|
4152
5098
|
//# sourceMappingURL=index.js.map
|