@opengeni/core 2.9.3 → 2.9.4-canary.1
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/domain/skill-imports.d.ts +1 -0
- package/dist/index.js +323 -259
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/domain/capabilities.ts +4 -21
- package/src/domain/github-skill-source.ts +90 -1
- package/src/domain/product-integration-skill.gen.ts +1 -1
- package/src/domain/skill-imports.ts +38 -2
- package/src/domain/skill-search.ts +3 -0
package/dist/index.js
CHANGED
|
@@ -4110,7 +4110,7 @@ var productIntegrationSkillFiles = [
|
|
|
4110
4110
|
},
|
|
4111
4111
|
{
|
|
4112
4112
|
"path": "references/product-shapes-and-ui.md",
|
|
4113
|
-
"content": "# Product shapes and UI\n\n## Choose the smallest suitable surface\n\nOpenGeni supports several product shapes. Select from the product experience and host stack rather than assuming every integration needs a custom chat:\n\n| Need | Likely surface | Product owns |\n| --- | --- | --- |\n| The complete OpenGeni experience is acceptable | Link or deep-link to stock OpenGeni | Entry point and product navigation |\n| Custom UI in any framework, mobile app, CLI, or automation | OpenGeni SDK or public API behind product backend | All user-facing presentation |\n| React product wants canonical session state without packaged visuals | Headless React session hooks and projections | Components, layout, and styling |\n| React product wants packaged chat/session controls | Focused styled React subpaths | Shell, domain UI, and theming |\n| Product exposes files, changes, terminal, or desktop compute | Optional workbench surfaces | Product shell and selected tabs |\n\nStart with the narrowest surface that preserves the desired experience. Do not mount the full workbench for an ordinary analytics chat. Do not rebuild session streaming, replay, queueing, approval, or timeline projection when a compatible package already supplies the needed behavior.\n\n## Evaluate reuse before writing chat UI\n\nFor React hosts, inspect the installed OpenGeni React package before creating replacement components. Its subpaths are composable, and the styled surfaces use scoped compiled CSS plus runtime theme and density tokens. Compare:\n\n- packaged components with customer theme tokens;\n- headless hooks with customer-native components; and\n- a fully custom SDK-driven UI.\n\nChoose based on UX requirements and dependency compatibility, then record why. Styling differences alone are not a reason to skip reusable components if their structure fits. Conversely, do not force a packaged component when the product needs a materially different interaction model.\n\nFor Svelte, SvelteKit, Vue, native mobile, or another non-React frontend, use the product's native component system. Keep the privileged OpenGeni client on a compatible backend boundary. A SvelteKit server route may use the TypeScript SDK directly; a non-JavaScript backend may use the public HTTP contract or a small compatible adapter. The browser still speaks to authenticated product routes.\n\n## Browser/backend split\n\nThe product browser normally sends product-shaped requests to its own same-origin backend. The backend authenticates, resolves the allowed mapping, and calls OpenGeni. Never bundle an organization key into frontend code.\n\nFor live sessions, preserve event sequence, reconnect, replay, and duplicate suppression. The SDK's stream and proxy helpers are preferred where compatible. Treat unknown additive event types as forward-compatible data rather than crashing the UI.\n\nUploads may send bytes directly to a short-lived signed storage URL returned by the trusted flow. That URL is narrow transfer authority, not the OpenGeni API key. Verify storage CORS for every intended browser origin.\n\n## Decide what the user sees\n\nOpenGeni's durable event stream can support different product projections:\n\n- final answer only;\n- assistant messages plus progress and status;\n- selected tool-call summaries;\n- approvals and structured human-input cards; or\n- a detailed operational timeline.\n\nThe customer frontend chooses which event types and fields to render. Hiding an event from the chat view does not remove it from OpenGeni's durable history or from authorized audit readers. Do not promise data erasure or secrecy from presentation filtering.\n\nEven a final-answer-only UI should surface states the user must act on: failure, cancellation, credit or policy denial, approval requests, human-input requests, reconnect status, and a way to retry safely. Avoid presenting tool failures as ordinary assistant prose when product state can represent them more clearly.\n\n## Fit the host product\n\nFollow existing navigation, accessibility, responsive, loading, error, observability, localization, and design-system conventions. Keep OpenGeni IDs behind product-native identifiers. Make the smallest dependency addition that improves correctness.\n\nThe integration should feel native to the customer product while retaining OpenGeni's session semantics. Framework adaptation is expected; protocol reimplementation is not a goal.\n"
|
|
4113
|
+
"content": "# Opening host-owned workbench tabs\n\nUse `SandboxWorkspace.openTabRequest={{ tab, requestId }}` to open a built-in or\nhost-injected tab from the host's UI. Increment `requestId` for each intentional\nopen, including repeated clicks on the same item. Keep artifact selection and\ninternal-link recognition in the host; the workbench only selects an available\ntab and expands the dock. Preserve modified clicks and external navigation.\nWhen handing off to a full-page artifact route, retain an explicit originating\nsession return path rather than relying on browser history.\n\n# Product shapes and UI\n\n## Choose the smallest suitable surface\n\nOpenGeni supports several product shapes. Select from the product experience and host stack rather than assuming every integration needs a custom chat:\n\n| Need | Likely surface | Product owns |\n| --- | --- | --- |\n| The complete OpenGeni experience is acceptable | Link or deep-link to stock OpenGeni | Entry point and product navigation |\n| Custom UI in any framework, mobile app, CLI, or automation | OpenGeni SDK or public API behind product backend | All user-facing presentation |\n| React product wants canonical session state without packaged visuals | Headless React session hooks and projections | Components, layout, and styling |\n| React product wants packaged chat/session controls | Focused styled React subpaths | Shell, domain UI, and theming |\n| Product exposes files, changes, terminal, or desktop compute | Optional workbench surfaces | Product shell and selected tabs |\n\nStart with the narrowest surface that preserves the desired experience. Do not mount the full workbench for an ordinary analytics chat. Do not rebuild session streaming, replay, queueing, approval, or timeline projection when a compatible package already supplies the needed behavior.\n\n## Evaluate reuse before writing chat UI\n\nFor React hosts, inspect the installed OpenGeni React package before creating replacement components. Its subpaths are composable, and the styled surfaces use scoped compiled CSS plus runtime theme and density tokens. Compare:\n\n- packaged components with customer theme tokens;\n- headless hooks with customer-native components; and\n- a fully custom SDK-driven UI.\n\nChoose based on UX requirements and dependency compatibility, then record why. Styling differences alone are not a reason to skip reusable components if their structure fits. Conversely, do not force a packaged component when the product needs a materially different interaction model.\n\nFor Svelte, SvelteKit, Vue, native mobile, or another non-React frontend, use the product's native component system. Keep the privileged OpenGeni client on a compatible backend boundary. A SvelteKit server route may use the TypeScript SDK directly; a non-JavaScript backend may use the public HTTP contract or a small compatible adapter. The browser still speaks to authenticated product routes.\n\n## Browser/backend split\n\nThe product browser normally sends product-shaped requests to its own same-origin backend. The backend authenticates, resolves the allowed mapping, and calls OpenGeni. Never bundle an organization key into frontend code.\n\nFor live sessions, preserve event sequence, reconnect, replay, and duplicate suppression. The SDK's stream and proxy helpers are preferred where compatible. Treat unknown additive event types as forward-compatible data rather than crashing the UI.\n\nUploads may send bytes directly to a short-lived signed storage URL returned by the trusted flow. That URL is narrow transfer authority, not the OpenGeni API key. Verify storage CORS for every intended browser origin.\n\n## Decide what the user sees\n\nOpenGeni's durable event stream can support different product projections:\n\n- final answer only;\n- assistant messages plus progress and status;\n- selected tool-call summaries;\n- approvals and structured human-input cards; or\n- a detailed operational timeline.\n\nThe customer frontend chooses which event types and fields to render. Hiding an event from the chat view does not remove it from OpenGeni's durable history or from authorized audit readers. Do not promise data erasure or secrecy from presentation filtering.\n\nEven a final-answer-only UI should surface states the user must act on: failure, cancellation, credit or policy denial, approval requests, human-input requests, reconnect status, and a way to retry safely. Avoid presenting tool failures as ordinary assistant prose when product state can represent them more clearly.\n\n## Fit the host product\n\nFollow existing navigation, accessibility, responsive, loading, error, observability, localization, and design-system conventions. Keep OpenGeni IDs behind product-native identifiers. Make the smallest dependency addition that improves correctness.\n\nThe integration should feel native to the customer product while retaining OpenGeni's session semantics. Framework adaptation is expected; protocol reimplementation is not a goal.\n"
|
|
4114
4114
|
},
|
|
4115
4115
|
{
|
|
4116
4116
|
"path": "references/runtime-profile-and-verification.md",
|
|
@@ -4747,7 +4747,7 @@ async function buildCapabilityCatalog(input) {
|
|
|
4747
4747
|
...configuredMcpCatalogItems(input.settings),
|
|
4748
4748
|
...providerIntegrationCatalogItems(socialConnections),
|
|
4749
4749
|
fikenCatalogItem(workspaceConnections.filter(isFikenConnection)),
|
|
4750
|
-
...curatedLibrarySkills,
|
|
4750
|
+
...curatedLibrarySkills.filter((item) => installedSkillById.has(item.id)),
|
|
4751
4751
|
...installedSkills.filter(
|
|
4752
4752
|
(skill) => skill.source !== "library" && skill.owners.some((owner) => owner.kind === "direct")
|
|
4753
4753
|
).map(installedSkillCatalogItem)
|
|
@@ -5369,9 +5369,6 @@ async function discoverMcpRegistryCapabilities(input) {
|
|
|
5369
5369
|
if (!item || seen.has(item.id)) {
|
|
5370
5370
|
continue;
|
|
5371
5371
|
}
|
|
5372
|
-
if (query && !catalogSearchText(item).includes(query)) {
|
|
5373
|
-
continue;
|
|
5374
|
-
}
|
|
5375
5372
|
seen.add(item.id);
|
|
5376
5373
|
items.push(item);
|
|
5377
5374
|
if (items.length >= limit) {
|
|
@@ -5379,7 +5376,7 @@ async function discoverMcpRegistryCapabilities(input) {
|
|
|
5379
5376
|
}
|
|
5380
5377
|
}
|
|
5381
5378
|
cursor = typeof page.metadata?.nextCursor === "string" ? page.metadata.nextCursor : void 0;
|
|
5382
|
-
if (!cursor) {
|
|
5379
|
+
if (items.length > 0 || !cursor) {
|
|
5383
5380
|
break;
|
|
5384
5381
|
}
|
|
5385
5382
|
}
|
|
@@ -6102,18 +6099,6 @@ function validUrl(value) {
|
|
|
6102
6099
|
return null;
|
|
6103
6100
|
}
|
|
6104
6101
|
}
|
|
6105
|
-
function catalogSearchText(item) {
|
|
6106
|
-
return [
|
|
6107
|
-
item.name,
|
|
6108
|
-
item.description,
|
|
6109
|
-
item.category,
|
|
6110
|
-
...item.tags,
|
|
6111
|
-
item.endpointUrl,
|
|
6112
|
-
item.homepageUrl,
|
|
6113
|
-
item.installUrl,
|
|
6114
|
-
JSON.stringify(item.metadata)
|
|
6115
|
-
].filter(Boolean).join(" ").toLowerCase();
|
|
6116
|
-
}
|
|
6117
6102
|
function capabilityInstallationRuntimeReady(item, installation) {
|
|
6118
6103
|
if (!installation || item.kind !== "mcp") {
|
|
6119
6104
|
return !!installation;
|
|
@@ -6160,6 +6145,10 @@ var gitCommit = /^[0-9a-f]{40,64}$/u;
|
|
|
6160
6145
|
var maxConcurrentBlobReads = 8;
|
|
6161
6146
|
async function resolveSkillImport(rawUrl, client) {
|
|
6162
6147
|
const parsed = parseSkillSource(rawUrl);
|
|
6148
|
+
if (parsed.source === "skills_sh" && client.downloadSnapshot) {
|
|
6149
|
+
const files2 = await client.downloadSnapshot(parsed.owner, parsed.repository, parsed.skillSlug);
|
|
6150
|
+
return buildResolvedImport(parsed, files2, parsed.skillSlug, null);
|
|
6151
|
+
}
|
|
6163
6152
|
const sourceCommit = await client.resolveCommit(parsed.owner, parsed.repository, parsed.ref);
|
|
6164
6153
|
if (!gitCommit.test(sourceCommit)) {
|
|
6165
6154
|
throw new HTTPException11(422, { message: "GitHub returned an invalid source commit" });
|
|
@@ -6199,6 +6188,9 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
6199
6188
|
}
|
|
6200
6189
|
return { path: relativeSkillPath(entry.path, sourcePath), content };
|
|
6201
6190
|
});
|
|
6191
|
+
return buildResolvedImport(parsed, files, sourcePath, sourceCommit);
|
|
6192
|
+
}
|
|
6193
|
+
function buildResolvedImport(parsed, files, sourcePath, commit) {
|
|
6202
6194
|
let artifact;
|
|
6203
6195
|
try {
|
|
6204
6196
|
artifact = buildPortableSkillArtifact3(files);
|
|
@@ -6208,7 +6200,13 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
6208
6200
|
});
|
|
6209
6201
|
}
|
|
6210
6202
|
const repositoryUrl = `https://github.com/${parsed.owner}/${parsed.repository}`;
|
|
6211
|
-
const
|
|
6203
|
+
const sourceCommit = commit ?? artifact.contentSha256;
|
|
6204
|
+
if (!commit && artifact.name.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") !== parsed.skillSlug.toLowerCase()) {
|
|
6205
|
+
throw new HTTPException11(422, {
|
|
6206
|
+
message: "The downloaded skill does not match the requested name"
|
|
6207
|
+
});
|
|
6208
|
+
}
|
|
6209
|
+
const sourceUrl = !commit ? `https://skills.sh/${parsed.owner}/${parsed.repository}/${parsed.skillSlug}` : sourcePath === "." ? `${repositoryUrl}/tree/${sourceCommit}` : `${repositoryUrl}/tree/${sourceCommit}/${encodeGitHubPath(sourcePath)}`;
|
|
6212
6210
|
const warnings = [];
|
|
6213
6211
|
if (!artifact.files.some((file) => /(^|\/)licen[cs]e(?:\.|$)/iu.test(file.path))) {
|
|
6214
6212
|
warnings.push("No license file was found inside the selected Skill folder.");
|
|
@@ -6221,6 +6219,7 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
6221
6219
|
}
|
|
6222
6220
|
return {
|
|
6223
6221
|
preview: {
|
|
6222
|
+
markdown: artifact.files.find((file) => file.path === "SKILL.md")?.content,
|
|
6224
6223
|
source: parsed.source,
|
|
6225
6224
|
sourceUrl,
|
|
6226
6225
|
repositoryUrl,
|
|
@@ -6582,6 +6581,7 @@ function normalizeResponse(payload, query, limit, owner) {
|
|
|
6582
6581
|
if (!isRecord2(skill) || typeof skill.id !== "string" || typeof skill.source !== "string")
|
|
6583
6582
|
throw invalid();
|
|
6584
6583
|
const parts = skill.id.split("/");
|
|
6584
|
+
if (parts.length === 2 && skill.source === parts[0]) continue;
|
|
6585
6585
|
if (parts.length !== 3 || !ownerPattern.test(parts[0]) || !parts.every((part) => segmentPattern.test(part)) || skill.source !== parts.slice(0, 2).join("/") || skill.skillId !== void 0 && skill.skillId !== parts[2] || typeof skill.name !== "string" || !skill.name.trim() || skill.name.length > 200 || /[\u0000-\u001f\u007f]/u.test(skill.name) || typeof skill.installs !== "number" || !Number.isSafeInteger(skill.installs) || skill.installs < 0 || owner !== void 0 && parts[0].toLowerCase() !== owner)
|
|
6586
6586
|
throw invalid();
|
|
6587
6587
|
if (seen.has(skill.id)) continue;
|
|
@@ -6611,13 +6611,42 @@ function parseRetryAfter(value) {
|
|
|
6611
6611
|
}
|
|
6612
6612
|
|
|
6613
6613
|
// src/domain/github-skill-source.ts
|
|
6614
|
+
import { HTTPException as HTTPException12 } from "hono/http-exception";
|
|
6614
6615
|
import { pinnedFetch as pinnedFetch2, readResponseJsonBounded as readResponseJsonBounded2, readResponseTextBounded } from "@opengeni/network";
|
|
6616
|
+
import {
|
|
6617
|
+
PORTABLE_SKILL_MAX_FILES as PORTABLE_SKILL_MAX_FILES2,
|
|
6618
|
+
PORTABLE_SKILL_MAX_TOTAL_BYTES as PORTABLE_SKILL_MAX_TOTAL_BYTES2
|
|
6619
|
+
} from "@opengeni/runtime/skill-library";
|
|
6615
6620
|
var githubApiBase = "https://api.github.com";
|
|
6616
6621
|
var githubRequestTimeoutMs = 15e3;
|
|
6617
6622
|
var githubMetadataMaxBytes = 4 * 1024 * 1024;
|
|
6618
6623
|
var githubBlobResponseMaxBytes = 512 * 1024;
|
|
6619
6624
|
function createGitHubSkillSourceClient(settings, requestJson = (path, maxBytes, label) => githubJson(settings, path, maxBytes, label)) {
|
|
6625
|
+
const readJson = requestJson;
|
|
6626
|
+
const cache = /* @__PURE__ */ new Map();
|
|
6627
|
+
const pending = /* @__PURE__ */ new Map();
|
|
6628
|
+
requestJson = async (path, maxBytes, label) => {
|
|
6629
|
+
const cached = cache.get(path);
|
|
6630
|
+
if (cached && cached.expires > Date.now()) return cached.payload;
|
|
6631
|
+
const existing = pending.get(path);
|
|
6632
|
+
if (existing) return existing;
|
|
6633
|
+
const task = readJson(path, maxBytes, label).then((payload) => {
|
|
6634
|
+
if (cache.size >= 128) cache.delete(cache.keys().next().value);
|
|
6635
|
+
cache.set(path, {
|
|
6636
|
+
payload,
|
|
6637
|
+
expires: Date.now() + (path.includes("/commits/") ? 6e4 : 36e5)
|
|
6638
|
+
});
|
|
6639
|
+
return payload;
|
|
6640
|
+
});
|
|
6641
|
+
pending.set(path, task);
|
|
6642
|
+
try {
|
|
6643
|
+
return await task;
|
|
6644
|
+
} finally {
|
|
6645
|
+
pending.delete(path);
|
|
6646
|
+
}
|
|
6647
|
+
};
|
|
6620
6648
|
return {
|
|
6649
|
+
downloadSnapshot: (owner, repository, slug) => downloadSkillSnapshot(settings, owner, repository, slug),
|
|
6621
6650
|
resolveCommit: async (owner, repository, ref) => {
|
|
6622
6651
|
const payload = recordValue(
|
|
6623
6652
|
await requestJson(
|
|
@@ -6681,6 +6710,39 @@ function createGitHubSkillSourceClient(settings, requestJson = (path, maxBytes,
|
|
|
6681
6710
|
}
|
|
6682
6711
|
};
|
|
6683
6712
|
}
|
|
6713
|
+
async function downloadSkillSnapshot(settings, owner, repository, slug) {
|
|
6714
|
+
const signal = AbortSignal.timeout(githubRequestTimeoutMs);
|
|
6715
|
+
const response = await pinnedFetch2(
|
|
6716
|
+
`https://skills.sh/api/download/${[owner, repository, slug].map(encodeURIComponent).join("/")}`,
|
|
6717
|
+
{ headers: { accept: "application/json" }, credentials: "omit", redirect: "manual", signal },
|
|
6718
|
+
settings,
|
|
6719
|
+
{ label: "Skill download", requireHttpsOutsideLocalTest: true }
|
|
6720
|
+
);
|
|
6721
|
+
if (!response.ok) {
|
|
6722
|
+
await response.body?.cancel();
|
|
6723
|
+
throw new HTTPException12(response.status === 429 ? 429 : 422, {
|
|
6724
|
+
message: response.status === 429 ? "skills.sh is busy. Try again shortly." : response.status === 404 ? "This skill has no downloadable snapshot. Import its GitHub folder URL instead." : "Could not download this skill from skills.sh. Try again shortly."
|
|
6725
|
+
});
|
|
6726
|
+
}
|
|
6727
|
+
const payload = recordValue(
|
|
6728
|
+
await readResponseJsonBounded2(
|
|
6729
|
+
response,
|
|
6730
|
+
PORTABLE_SKILL_MAX_TOTAL_BYTES2 * 6 + 128e3,
|
|
6731
|
+
"Skill download",
|
|
6732
|
+
{ signal }
|
|
6733
|
+
),
|
|
6734
|
+
"Skill download"
|
|
6735
|
+
);
|
|
6736
|
+
if (!Array.isArray(payload.files) || payload.files.length === 0 || payload.files.length > PORTABLE_SKILL_MAX_FILES2) {
|
|
6737
|
+
throw new HTTPException12(422, { message: "The downloaded skill has an invalid file list" });
|
|
6738
|
+
}
|
|
6739
|
+
return payload.files.map((file) => {
|
|
6740
|
+
if (!file || typeof file !== "object" || typeof file.path !== "string" || typeof file.contents !== "string") {
|
|
6741
|
+
throw new HTTPException12(422, { message: "The downloaded skill contains an invalid file" });
|
|
6742
|
+
}
|
|
6743
|
+
return { path: file.path, content: file.contents };
|
|
6744
|
+
});
|
|
6745
|
+
}
|
|
6684
6746
|
async function githubJson(settings, path, maxBytes, label) {
|
|
6685
6747
|
const controller = new AbortController();
|
|
6686
6748
|
const timeout = setTimeout(() => controller.abort(), githubRequestTimeoutMs);
|
|
@@ -6703,7 +6765,9 @@ async function githubJson(settings, path, maxBytes, label) {
|
|
|
6703
6765
|
await readResponseTextBounded(response, 8192, `${label} error`).catch(() => void 0);
|
|
6704
6766
|
if (response.status === 404) throw new Error(`${label} was not found or is not public`);
|
|
6705
6767
|
if (response.status === 403 || response.status === 429) {
|
|
6706
|
-
throw new
|
|
6768
|
+
throw new HTTPException12(429, {
|
|
6769
|
+
message: "GitHub's public request limit has been reached. Skill search is still available; try previewing again after the limit resets."
|
|
6770
|
+
});
|
|
6707
6771
|
}
|
|
6708
6772
|
throw new Error(`${label} failed with HTTP ${response.status}`);
|
|
6709
6773
|
}
|
|
@@ -6730,12 +6794,12 @@ import {
|
|
|
6730
6794
|
variableSetVariableNameReservation
|
|
6731
6795
|
} from "@opengeni/contracts";
|
|
6732
6796
|
import { getVariableSet as getVariableSet2, recordAuditEvent } from "@opengeni/db";
|
|
6733
|
-
import { HTTPException as
|
|
6797
|
+
import { HTTPException as HTTPException13 } from "hono/http-exception";
|
|
6734
6798
|
var MAX_ENVIRONMENTS_PER_WORKSPACE = 25;
|
|
6735
6799
|
var MAX_VARIABLES_PER_ENVIRONMENT = 100;
|
|
6736
6800
|
function assertAllowedVariableSetVariableName(name) {
|
|
6737
6801
|
if (variableSetVariableNameReservation(name)) {
|
|
6738
|
-
throw new
|
|
6802
|
+
throw new HTTPException13(422, {
|
|
6739
6803
|
message: `reserved variable set variable name / reserved environment variable name: ${name}`
|
|
6740
6804
|
});
|
|
6741
6805
|
}
|
|
@@ -6744,7 +6808,7 @@ var assertAllowedEnvironmentVariableName = assertAllowedVariableSetVariableName;
|
|
|
6744
6808
|
function requireVariableSetEncryption(settings) {
|
|
6745
6809
|
const key = environmentsEncryptionKeyBytes2(settings);
|
|
6746
6810
|
if (!key) {
|
|
6747
|
-
throw new
|
|
6811
|
+
throw new HTTPException13(503, {
|
|
6748
6812
|
message: "variable sets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
|
|
6749
6813
|
});
|
|
6750
6814
|
}
|
|
@@ -6762,7 +6826,7 @@ async function requireVariableSetForApi(db, grant, variableSetId) {
|
|
|
6762
6826
|
variableSetId
|
|
6763
6827
|
);
|
|
6764
6828
|
if (!variableSet) {
|
|
6765
|
-
throw new
|
|
6829
|
+
throw new HTTPException13(404, { message: "variableSet not found" });
|
|
6766
6830
|
}
|
|
6767
6831
|
return variableSet;
|
|
6768
6832
|
}
|
|
@@ -6778,7 +6842,7 @@ async function validateVariableSetAttachment(deps, grant, workspaceId, variableS
|
|
|
6778
6842
|
variableSetId
|
|
6779
6843
|
);
|
|
6780
6844
|
if (!variableSet) {
|
|
6781
|
-
throw new
|
|
6845
|
+
throw new HTTPException13(422, { message: "unknown variableSetId" });
|
|
6782
6846
|
}
|
|
6783
6847
|
return variableSet;
|
|
6784
6848
|
}
|
|
@@ -6818,7 +6882,7 @@ import {
|
|
|
6818
6882
|
RigChangeTransitionError,
|
|
6819
6883
|
updateScopedRig
|
|
6820
6884
|
} from "@opengeni/db";
|
|
6821
|
-
import { HTTPException as
|
|
6885
|
+
import { HTTPException as HTTPException14 } from "hono/http-exception";
|
|
6822
6886
|
import { boundedParallelMap } from "@opengeni/runtime/mcp-network";
|
|
6823
6887
|
var MAX_RIGS_PER_WORKSPACE = 50;
|
|
6824
6888
|
var MAX_CHECKS_PER_RIG = 100;
|
|
@@ -6857,21 +6921,21 @@ function rigActorForGrant(grant) {
|
|
|
6857
6921
|
async function requireRigForApi(db, grant, rigId) {
|
|
6858
6922
|
const rig = await getRig2(db, grant, rigId);
|
|
6859
6923
|
if (!rig) {
|
|
6860
|
-
throw new
|
|
6924
|
+
throw new HTTPException14(404, { message: "rig not found" });
|
|
6861
6925
|
}
|
|
6862
6926
|
return rig;
|
|
6863
6927
|
}
|
|
6864
6928
|
async function requireRigChangeForApi(db, workspaceId, rigId, changeId) {
|
|
6865
6929
|
const change = await getRigChange(db, workspaceId, changeId);
|
|
6866
6930
|
if (!change || change.rigId !== rigId) {
|
|
6867
|
-
throw new
|
|
6931
|
+
throw new HTTPException14(404, { message: "rig change not found" });
|
|
6868
6932
|
}
|
|
6869
6933
|
return change;
|
|
6870
6934
|
}
|
|
6871
6935
|
function trimmedRigName(name) {
|
|
6872
6936
|
const trimmed = name.trim();
|
|
6873
6937
|
if (!trimmed) {
|
|
6874
|
-
throw new
|
|
6938
|
+
throw new HTTPException14(422, { message: "rig name is required" });
|
|
6875
6939
|
}
|
|
6876
6940
|
return trimmed;
|
|
6877
6941
|
}
|
|
@@ -6882,7 +6946,7 @@ function assertUniqueCheckNames(checks) {
|
|
|
6882
6946
|
const seen = /* @__PURE__ */ new Set();
|
|
6883
6947
|
for (const check of checks) {
|
|
6884
6948
|
if (seen.has(check.name)) {
|
|
6885
|
-
throw new
|
|
6949
|
+
throw new HTTPException14(422, {
|
|
6886
6950
|
message: `duplicate rig check name: ${check.name}`
|
|
6887
6951
|
});
|
|
6888
6952
|
}
|
|
@@ -6902,12 +6966,12 @@ async function assertVariableSetsExist(db, access, rigScope, ids) {
|
|
|
6902
6966
|
for (const id of unique) {
|
|
6903
6967
|
const variableSet = await getVariableSet3(db, access, id);
|
|
6904
6968
|
if (!variableSet) {
|
|
6905
|
-
throw new
|
|
6969
|
+
throw new HTTPException14(422, {
|
|
6906
6970
|
message: `unknown defaultVariableSetId: ${id}`
|
|
6907
6971
|
});
|
|
6908
6972
|
}
|
|
6909
6973
|
if (!variableSetScopeAllowedForRig(rigScope, variableSet.scope)) {
|
|
6910
|
-
throw new
|
|
6974
|
+
throw new HTTPException14(422, {
|
|
6911
6975
|
message: `${rigScope}-scoped rigs cannot use ${variableSet.scope}-scoped variable sets`
|
|
6912
6976
|
});
|
|
6913
6977
|
}
|
|
@@ -6926,12 +6990,12 @@ async function createRigForApi(deps, grant, payload, options = {}) {
|
|
|
6926
6990
|
payload.defaultVariableSetIds.length > 0 ? payload.defaultVariableSetIds : void 0
|
|
6927
6991
|
);
|
|
6928
6992
|
if (await countRigs(deps.db, grant, payload.scope) >= MAX_RIGS_PER_WORKSPACE) {
|
|
6929
|
-
throw new
|
|
6993
|
+
throw new HTTPException14(422, {
|
|
6930
6994
|
message: `a workspace supports at most ${MAX_RIGS_PER_WORKSPACE} rigs`
|
|
6931
6995
|
});
|
|
6932
6996
|
}
|
|
6933
6997
|
if (await getRigByName(deps.db, grant, name, payload.scope)) {
|
|
6934
|
-
throw new
|
|
6998
|
+
throw new HTTPException14(409, {
|
|
6935
6999
|
message: `rig name is already in use: ${name}`
|
|
6936
7000
|
});
|
|
6937
7001
|
}
|
|
@@ -6967,7 +7031,7 @@ async function updateRigForApi(deps, grant, rig, payload, options = {}) {
|
|
|
6967
7031
|
if (name !== void 0 && name !== rig.name) {
|
|
6968
7032
|
const existing = await getRigByName(deps.db, grant, name, rig.scope);
|
|
6969
7033
|
if (existing && existing.id !== rig.id) {
|
|
6970
|
-
throw new
|
|
7034
|
+
throw new HTTPException14(409, {
|
|
6971
7035
|
message: `rig name is already in use: ${name}`
|
|
6972
7036
|
});
|
|
6973
7037
|
}
|
|
@@ -6993,7 +7057,7 @@ async function deleteRigForApi(deps, grant, rig, options = {}) {
|
|
|
6993
7057
|
let current = error;
|
|
6994
7058
|
while (current instanceof Error) {
|
|
6995
7059
|
if (current.message.includes("active sessions")) {
|
|
6996
|
-
throw new
|
|
7060
|
+
throw new HTTPException14(409, { message: current.message });
|
|
6997
7061
|
}
|
|
6998
7062
|
current = current.cause;
|
|
6999
7063
|
}
|
|
@@ -7008,7 +7072,7 @@ async function deleteRigForApi(deps, grant, rig, options = {}) {
|
|
|
7008
7072
|
async function proposeRigChangeForApi(deps, grant, rig, request, options = {}) {
|
|
7009
7073
|
const workspaceId = rig.workspaceId;
|
|
7010
7074
|
if (!rig.activeVersion) {
|
|
7011
|
-
throw new
|
|
7075
|
+
throw new HTTPException14(422, {
|
|
7012
7076
|
message: "rig has no active version to base a change on"
|
|
7013
7077
|
});
|
|
7014
7078
|
}
|
|
@@ -7060,37 +7124,37 @@ async function promoteChangeWithActiveCas(deps, workspaceId, rigId, changeId, in
|
|
|
7060
7124
|
return await createRigVersionForChangePromotion(deps.db, workspaceId, rigId, changeId, input);
|
|
7061
7125
|
} catch (error) {
|
|
7062
7126
|
if (error instanceof RigActiveVersionChangedError) {
|
|
7063
|
-
throw new
|
|
7127
|
+
throw new HTTPException14(409, {
|
|
7064
7128
|
message: `rig moved since this change was verified (base ${error.expectedVersionId}, now ${error.actualVersionId ?? "none"}); re-verify before promoting`
|
|
7065
7129
|
});
|
|
7066
7130
|
}
|
|
7067
7131
|
if (error instanceof RigChangeTransitionError) {
|
|
7068
|
-
throw new
|
|
7132
|
+
throw new HTTPException14(409, { message: error.message });
|
|
7069
7133
|
}
|
|
7070
7134
|
throw error;
|
|
7071
7135
|
}
|
|
7072
7136
|
}
|
|
7073
7137
|
async function promoteSetupAppendChange(deps, grant, rig, change) {
|
|
7074
7138
|
if (change.kind !== "setup_append") {
|
|
7075
|
-
throw new
|
|
7139
|
+
throw new HTTPException14(422, {
|
|
7076
7140
|
message: "only setup_append changes auto-promote through this path"
|
|
7077
7141
|
});
|
|
7078
7142
|
}
|
|
7079
7143
|
if (change.status !== "proposed" && change.status !== "verifying") {
|
|
7080
|
-
throw new
|
|
7144
|
+
throw new HTTPException14(409, {
|
|
7081
7145
|
message: `rig change is ${change.status}; cannot promote`
|
|
7082
7146
|
});
|
|
7083
7147
|
}
|
|
7084
7148
|
if (!change.baseVersionId) {
|
|
7085
|
-
throw new
|
|
7149
|
+
throw new HTTPException14(422, { message: "rig change has no base version" });
|
|
7086
7150
|
}
|
|
7087
7151
|
const base = await getRigVersion2(deps.db, rig.workspaceId, rig.id, change.baseVersionId);
|
|
7088
7152
|
if (!base) {
|
|
7089
|
-
throw new
|
|
7153
|
+
throw new HTTPException14(404, { message: "base rig version not found" });
|
|
7090
7154
|
}
|
|
7091
7155
|
const payload = change.payload;
|
|
7092
7156
|
if (typeof payload.command !== "string" || !payload.command.trim()) {
|
|
7093
|
-
throw new
|
|
7157
|
+
throw new HTTPException14(422, {
|
|
7094
7158
|
message: "setup_append change is missing command"
|
|
7095
7159
|
});
|
|
7096
7160
|
}
|
|
@@ -7138,26 +7202,26 @@ async function promoteSetupAppendChange(deps, grant, rig, change) {
|
|
|
7138
7202
|
}
|
|
7139
7203
|
async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, change) {
|
|
7140
7204
|
if (change.kind !== "definition_edit") {
|
|
7141
|
-
throw new
|
|
7205
|
+
throw new HTTPException14(422, {
|
|
7142
7206
|
message: "only definition_edit changes use explicit promote"
|
|
7143
7207
|
});
|
|
7144
7208
|
}
|
|
7145
7209
|
if (change.status !== "proposed") {
|
|
7146
|
-
throw new
|
|
7210
|
+
throw new HTTPException14(409, {
|
|
7147
7211
|
message: `rig change is ${change.status}; cannot promote`
|
|
7148
7212
|
});
|
|
7149
7213
|
}
|
|
7150
7214
|
if (change.verification?.passed !== true) {
|
|
7151
|
-
throw new
|
|
7215
|
+
throw new HTTPException14(422, {
|
|
7152
7216
|
message: "definition_edit change must pass verification before promote"
|
|
7153
7217
|
});
|
|
7154
7218
|
}
|
|
7155
7219
|
if (!change.baseVersionId) {
|
|
7156
|
-
throw new
|
|
7220
|
+
throw new HTTPException14(422, { message: "rig change has no base version" });
|
|
7157
7221
|
}
|
|
7158
7222
|
const base = await getRigVersion2(deps.db, rig.workspaceId, rig.id, change.baseVersionId);
|
|
7159
7223
|
if (!base) {
|
|
7160
|
-
throw new
|
|
7224
|
+
throw new HTTPException14(404, { message: "base rig version not found" });
|
|
7161
7225
|
}
|
|
7162
7226
|
const payload = change.payload;
|
|
7163
7227
|
const nextDefinition = {
|
|
@@ -7204,7 +7268,7 @@ async function promoteVerifiedDefinitionEditChangeForApi(deps, grant, rig, chang
|
|
|
7204
7268
|
}
|
|
7205
7269
|
async function createRigVersionForApi(deps, grant, rig, payload) {
|
|
7206
7270
|
if (!rig.activeVersion) {
|
|
7207
|
-
throw new
|
|
7271
|
+
throw new HTTPException14(422, { message: "rig has no active version" });
|
|
7208
7272
|
}
|
|
7209
7273
|
assertUniqueCheckNames(payload.checks);
|
|
7210
7274
|
await assertVariableSetsExist(
|
|
@@ -7441,13 +7505,13 @@ import {
|
|
|
7441
7505
|
areGitHubRepositoriesAllowedForWorkspace,
|
|
7442
7506
|
requireFileForSubject
|
|
7443
7507
|
} from "@opengeni/db";
|
|
7444
|
-
import { HTTPException as
|
|
7508
|
+
import { HTTPException as HTTPException15 } from "hono/http-exception";
|
|
7445
7509
|
function validateToolRefs(tools, settings) {
|
|
7446
7510
|
const mcpServerIds = new Set(settings.mcpServers.map((server) => server.id));
|
|
7447
7511
|
const out = [];
|
|
7448
7512
|
for (const tool of tools) {
|
|
7449
7513
|
if (tool.kind !== "mcp") {
|
|
7450
|
-
throw new
|
|
7514
|
+
throw new HTTPException15(422, {
|
|
7451
7515
|
message: `unsupported tool kind: ${tool.kind}`
|
|
7452
7516
|
});
|
|
7453
7517
|
}
|
|
@@ -7456,7 +7520,7 @@ function validateToolRefs(tools, settings) {
|
|
|
7456
7520
|
if (optional) {
|
|
7457
7521
|
continue;
|
|
7458
7522
|
}
|
|
7459
|
-
throw new
|
|
7523
|
+
throw new HTTPException15(422, { message: `unknown MCP server id: ${tool.id}` });
|
|
7460
7524
|
}
|
|
7461
7525
|
out.push({
|
|
7462
7526
|
kind: "mcp",
|
|
@@ -7494,7 +7558,7 @@ function assertToolRefsSubset(requested, allowed, message = "requested tools exc
|
|
|
7494
7558
|
const allowedIds = new Set(allowed.map((tool) => `${tool.kind}:${tool.id}`));
|
|
7495
7559
|
const widened = requested.find((tool) => !allowedIds.has(`${tool.kind}:${tool.id}`));
|
|
7496
7560
|
if (widened) {
|
|
7497
|
-
throw new
|
|
7561
|
+
throw new HTTPException15(403, { message: `${message}: ${widened.id}` });
|
|
7498
7562
|
}
|
|
7499
7563
|
}
|
|
7500
7564
|
function validateToolRefsForSessionPolicy(input) {
|
|
@@ -7523,7 +7587,7 @@ function normalizeResources(resources) {
|
|
|
7523
7587
|
try {
|
|
7524
7588
|
normalizedUri = normalizeRepositoryTransportUri(resource.uri);
|
|
7525
7589
|
} catch (error) {
|
|
7526
|
-
throw new
|
|
7590
|
+
throw new HTTPException15(422, {
|
|
7527
7591
|
message: error instanceof Error ? error.message : "invalid repository URI"
|
|
7528
7592
|
});
|
|
7529
7593
|
}
|
|
@@ -7532,14 +7596,14 @@ function normalizeResources(resources) {
|
|
|
7532
7596
|
);
|
|
7533
7597
|
const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
|
|
7534
7598
|
if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
|
|
7535
|
-
throw new
|
|
7599
|
+
throw new HTTPException15(422, {
|
|
7536
7600
|
message: "repository credential bindings and access intent require a Git provider"
|
|
7537
7601
|
});
|
|
7538
7602
|
}
|
|
7539
7603
|
if (credentialProvider && credentialBindingId) {
|
|
7540
7604
|
const boundProvider = credentialBindingProviders.get(credentialBindingId);
|
|
7541
7605
|
if (boundProvider && boundProvider !== credentialProvider) {
|
|
7542
|
-
throw new
|
|
7606
|
+
throw new HTTPException15(422, {
|
|
7543
7607
|
message: `credential binding ${credentialBindingId} is assigned to multiple Git providers`
|
|
7544
7608
|
});
|
|
7545
7609
|
}
|
|
@@ -7567,7 +7631,7 @@ function normalizeResources(resources) {
|
|
|
7567
7631
|
const mountCollisionKey = normalized.mountPath ? resourceMountPathCollisionKey(normalized.mountPath) : void 0;
|
|
7568
7632
|
const mounted = mountCollisionKey ? mountPaths.get(mountCollisionKey) : void 0;
|
|
7569
7633
|
if (mounted && mounted !== key) {
|
|
7570
|
-
throw new
|
|
7634
|
+
throw new HTTPException15(422, {
|
|
7571
7635
|
message: `duplicate resource mount path: ${normalized.mountPath}`
|
|
7572
7636
|
});
|
|
7573
7637
|
}
|
|
@@ -7577,7 +7641,7 @@ function normalizeResources(resources) {
|
|
|
7577
7641
|
const identity = resourceIdentityKey(normalized);
|
|
7578
7642
|
const seenIdentity = identities.get(identity);
|
|
7579
7643
|
if (seenIdentity && seenIdentity !== key) {
|
|
7580
|
-
throw new
|
|
7644
|
+
throw new HTTPException15(422, {
|
|
7581
7645
|
message: `duplicate resource with different settings: ${identity}`
|
|
7582
7646
|
});
|
|
7583
7647
|
}
|
|
@@ -7594,7 +7658,7 @@ function mergeResourceRefs(existing, additions) {
|
|
|
7594
7658
|
return mergeContractResourceRefs(existing, additions, { rejectConflicts: true });
|
|
7595
7659
|
} catch (error) {
|
|
7596
7660
|
if (error instanceof ResourceRefConflictError) {
|
|
7597
|
-
throw new
|
|
7661
|
+
throw new HTTPException15(422, { message: error.message });
|
|
7598
7662
|
}
|
|
7599
7663
|
throw error;
|
|
7600
7664
|
}
|
|
@@ -7618,14 +7682,14 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
7618
7682
|
for (const resource of resources) {
|
|
7619
7683
|
if (!isPersonalGitHubRepositoryCandidate(resource)) {
|
|
7620
7684
|
if (resource.kind === "repository" && resource.connectionType === "github_personal") {
|
|
7621
|
-
throw new
|
|
7685
|
+
throw new HTTPException15(422, {
|
|
7622
7686
|
message: "personal GitHub repository resources require the dedicated GitHub provider"
|
|
7623
7687
|
});
|
|
7624
7688
|
}
|
|
7625
7689
|
continue;
|
|
7626
7690
|
}
|
|
7627
7691
|
if (!PERSONAL_GITHUB_BINDING_ID_PATTERN.test(resource.credentialBindingId) || typeof resource.repositoryId !== "string" || !/^[1-9]\d*$/u.test(resource.repositoryId) || resource.access !== "read" && resource.access !== "write" || resource.installationId !== void 0 || resource.githubInstallationId !== void 0 || resource.githubRepositoryId !== void 0 || resource.connectionId !== void 0 || resource.projectId !== void 0) {
|
|
7628
|
-
throw new
|
|
7692
|
+
throw new HTTPException15(422, {
|
|
7629
7693
|
message: "personal GitHub repository resources require one opaque binding, provider repository id, and explicit read or write access"
|
|
7630
7694
|
});
|
|
7631
7695
|
}
|
|
@@ -7633,10 +7697,10 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
7633
7697
|
try {
|
|
7634
7698
|
uri = new URL(resource.uri);
|
|
7635
7699
|
} catch {
|
|
7636
|
-
throw new
|
|
7700
|
+
throw new HTTPException15(422, { message: "personal GitHub repository URI is invalid" });
|
|
7637
7701
|
}
|
|
7638
7702
|
if (uri.protocol !== "https:" || uri.hostname !== "github.com" || uri.port !== "" || uri.username !== "" || uri.password !== "" || uri.search !== "" || uri.hash !== "" || uri.pathname.endsWith(".git") || uri.pathname.split("/").filter(Boolean).length !== 2 || resource.uri !== `${uri.origin}${uri.pathname.replace(/\/$/u, "")}`) {
|
|
7639
|
-
throw new
|
|
7703
|
+
throw new HTTPException15(422, {
|
|
7640
7704
|
message: "personal GitHub repository resources require a canonical GitHub HTTPS URI"
|
|
7641
7705
|
});
|
|
7642
7706
|
}
|
|
@@ -7646,7 +7710,7 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
7646
7710
|
for (const resource of selected) {
|
|
7647
7711
|
const key = `${resource.credentialBindingId}\0${resource.repositoryId}`;
|
|
7648
7712
|
if (identities.has(key)) {
|
|
7649
|
-
throw new
|
|
7713
|
+
throw new HTTPException15(422, {
|
|
7650
7714
|
message: "personal GitHub repository resources must not contain duplicates"
|
|
7651
7715
|
});
|
|
7652
7716
|
}
|
|
@@ -7657,7 +7721,7 @@ function personalGitHubRepositoryResources(resources) {
|
|
|
7657
7721
|
function validateGitHubRepositorySelectionShape(resources) {
|
|
7658
7722
|
const installationIds = validateGitHubRepositorySelectionShapes(resources);
|
|
7659
7723
|
if (installationIds.length > 1) {
|
|
7660
|
-
throw new
|
|
7724
|
+
throw new HTTPException15(422, {
|
|
7661
7725
|
message: "GitHub App repository resources must belong to one installation"
|
|
7662
7726
|
});
|
|
7663
7727
|
}
|
|
@@ -7682,7 +7746,7 @@ function gitHubRepositorySelections(resources) {
|
|
|
7682
7746
|
const installationId = positiveInteger2(installationRaw);
|
|
7683
7747
|
const repositoryId = positiveInteger2(repositoryRaw);
|
|
7684
7748
|
if (!installationId || !repositoryId) {
|
|
7685
|
-
throw new
|
|
7749
|
+
throw new HTTPException15(422, {
|
|
7686
7750
|
message: "GitHub App repository resources require positive github_installation_id and github_repository_id"
|
|
7687
7751
|
});
|
|
7688
7752
|
}
|
|
@@ -7704,14 +7768,14 @@ async function validateGitHubRepositorySelection(db, workspaceId, resources) {
|
|
|
7704
7768
|
installationId,
|
|
7705
7769
|
repositoryIds
|
|
7706
7770
|
)) {
|
|
7707
|
-
throw new
|
|
7771
|
+
throw new HTTPException15(422, {
|
|
7708
7772
|
message: "GitHub App repository resources must be authorized for a GitHub App installation linked to this workspace"
|
|
7709
7773
|
});
|
|
7710
7774
|
}
|
|
7711
7775
|
}
|
|
7712
7776
|
}
|
|
7713
7777
|
function isAuthoritativeGitHubRepositorySelectionError(error) {
|
|
7714
|
-
return error instanceof
|
|
7778
|
+
return error instanceof HTTPException15 && error.status === 422;
|
|
7715
7779
|
}
|
|
7716
7780
|
async function validateFileResources(db, accountId, workspaceId, subjectId, resources) {
|
|
7717
7781
|
const fileIds = /* @__PURE__ */ new Set();
|
|
@@ -7720,7 +7784,7 @@ async function validateFileResources(db, accountId, workspaceId, subjectId, reso
|
|
|
7720
7784
|
continue;
|
|
7721
7785
|
}
|
|
7722
7786
|
if (fileIds.has(resource.fileId)) {
|
|
7723
|
-
throw new
|
|
7787
|
+
throw new HTTPException15(422, { message: `duplicate file resource: ${resource.fileId}` });
|
|
7724
7788
|
}
|
|
7725
7789
|
fileIds.add(resource.fileId);
|
|
7726
7790
|
const file = await requireFileForSubject(db, {
|
|
@@ -7730,10 +7794,10 @@ async function validateFileResources(db, accountId, workspaceId, subjectId, reso
|
|
|
7730
7794
|
fileId: resource.fileId
|
|
7731
7795
|
}).catch(() => null);
|
|
7732
7796
|
if (!file) {
|
|
7733
|
-
throw new
|
|
7797
|
+
throw new HTTPException15(422, { message: `unknown file resource: ${resource.fileId}` });
|
|
7734
7798
|
}
|
|
7735
7799
|
if (file.status !== "ready") {
|
|
7736
|
-
throw new
|
|
7800
|
+
throw new HTTPException15(422, {
|
|
7737
7801
|
message: `file resource ${resource.fileId} is ${file.status}`
|
|
7738
7802
|
});
|
|
7739
7803
|
}
|
|
@@ -7744,7 +7808,7 @@ function normalizeMountPath(path) {
|
|
|
7744
7808
|
return normalizeResourceMountPath(path);
|
|
7745
7809
|
} catch (error) {
|
|
7746
7810
|
if (!(error instanceof ResourceMountPathError)) throw error;
|
|
7747
|
-
throw new
|
|
7811
|
+
throw new HTTPException15(422, { message: `invalid resource mount path: ${path}` });
|
|
7748
7812
|
}
|
|
7749
7813
|
}
|
|
7750
7814
|
function positiveInteger2(value) {
|
|
@@ -8825,11 +8889,11 @@ import {
|
|
|
8825
8889
|
withWorkspaceSubjectRls as withWorkspaceSubjectRls5,
|
|
8826
8890
|
resolveXaiProviderAccountAuthoritySnapshotForAcceptance
|
|
8827
8891
|
} from "@opengeni/db";
|
|
8828
|
-
import { HTTPException as
|
|
8892
|
+
import { HTTPException as HTTPException21 } from "hono/http-exception";
|
|
8829
8893
|
import { isDeepStrictEqual } from "util";
|
|
8830
8894
|
|
|
8831
8895
|
// src/domain/host-mcp-task-admission.ts
|
|
8832
|
-
import { HTTPException as
|
|
8896
|
+
import { HTTPException as HTTPException16 } from "hono/http-exception";
|
|
8833
8897
|
import {
|
|
8834
8898
|
HostMcpBindingDefinition,
|
|
8835
8899
|
HostMcpCreateSelections
|
|
@@ -8842,7 +8906,7 @@ import {
|
|
|
8842
8906
|
function prepareHostMcpTaskAdmission(input) {
|
|
8843
8907
|
const selections = HostMcpCreateSelections.parse(input.selections);
|
|
8844
8908
|
if (!input.authorization || input.authorization.grant.accountId !== input.grant.accountId || input.authorization.grant.subjectId !== input.grant.subjectId || input.authorization?.grant.workspaceId !== input.grant.workspaceId || input.grant.metadata?.["sessionId"] || !hasPermission(input.authorization?.grant.permissions ?? [], "connections:read") || !hasPermission(input.grant.permissions, "connections:read"))
|
|
8845
|
-
throw new
|
|
8909
|
+
throw new HTTPException16(403, {
|
|
8846
8910
|
message: "Host task selection requires a verified owner"
|
|
8847
8911
|
});
|
|
8848
8912
|
const reauthorize = prepareHostMcpOwnerAuthorization(
|
|
@@ -8855,7 +8919,7 @@ function prepareHostMcpTaskAdmission(input) {
|
|
|
8855
8919
|
(candidate) => candidate.id === selection.serverId
|
|
8856
8920
|
);
|
|
8857
8921
|
if (!server?.url || server.connectionRef?.authoritySource !== "host" || !server.connectionRef.hostBinding || !input.tools.some((tool) => tool.kind === "mcp" && tool.id === selection.serverId))
|
|
8858
|
-
throw new
|
|
8922
|
+
throw new HTTPException16(422, {
|
|
8859
8923
|
message: "Host task selection must match a selected configured server"
|
|
8860
8924
|
});
|
|
8861
8925
|
assertHostMcpAuthoritySourceAdmissionEnabled(input.settings, server.connectionRef);
|
|
@@ -8878,7 +8942,7 @@ function prepareHostMcpTaskAdmission(input) {
|
|
|
8878
8942
|
if (prepared.length) await captureHostMcpTaskAuthorities(tx, owner, task, prepared);
|
|
8879
8943
|
} catch (error) {
|
|
8880
8944
|
if (error instanceof HostMcpDelegationAuthorityError)
|
|
8881
|
-
throw new
|
|
8945
|
+
throw new HTTPException16(403, { message: error.message });
|
|
8882
8946
|
throw error;
|
|
8883
8947
|
}
|
|
8884
8948
|
};
|
|
@@ -9019,14 +9083,14 @@ import {
|
|
|
9019
9083
|
publishDurableSessionEvents as publishDurableSessionEvents2,
|
|
9020
9084
|
publishDurableWorkspaceControlEvent
|
|
9021
9085
|
} from "@opengeni/events";
|
|
9022
|
-
import { HTTPException as
|
|
9086
|
+
import { HTTPException as HTTPException20 } from "hono/http-exception";
|
|
9023
9087
|
|
|
9024
9088
|
// src/domain/external-creation-attribution.ts
|
|
9025
|
-
import { HTTPException as
|
|
9089
|
+
import { HTTPException as HTTPException17 } from "hono/http-exception";
|
|
9026
9090
|
var EXTERNAL_CREATION_ATTRIBUTION_KEY = "opengeniExternalCreationAttribution";
|
|
9027
9091
|
function externalCreationMetadata(metadata, authorization, grant) {
|
|
9028
9092
|
if (metadata && Object.hasOwn(metadata, EXTERNAL_CREATION_ATTRIBUTION_KEY)) {
|
|
9029
|
-
throw new
|
|
9093
|
+
throw new HTTPException17(422, {
|
|
9030
9094
|
message: `${EXTERNAL_CREATION_ATTRIBUTION_KEY} is server-owned`
|
|
9031
9095
|
});
|
|
9032
9096
|
}
|
|
@@ -9042,11 +9106,11 @@ import {
|
|
|
9042
9106
|
numberTimelineAnnotations
|
|
9043
9107
|
} from "@opengeni/contracts";
|
|
9044
9108
|
import { getSessionEvent } from "@opengeni/db";
|
|
9045
|
-
import { HTTPException as
|
|
9109
|
+
import { HTTPException as HTTPException18 } from "hono/http-exception";
|
|
9046
9110
|
var SOURCE_CONTEXT_BYTES = 160;
|
|
9047
9111
|
var ANSI_SEQUENCE = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
|
|
9048
9112
|
function invalidAnnotationSource() {
|
|
9049
|
-
throw new
|
|
9113
|
+
throw new HTTPException18(422, { message: "Invalid timeline annotation source" });
|
|
9050
9114
|
}
|
|
9051
9115
|
function safeJson(value) {
|
|
9052
9116
|
try {
|
|
@@ -9196,7 +9260,7 @@ import {
|
|
|
9196
9260
|
import {
|
|
9197
9261
|
getConnectionMetadata as getConnectionMetadata3
|
|
9198
9262
|
} from "@opengeni/db";
|
|
9199
|
-
import { HTTPException as
|
|
9263
|
+
import { HTTPException as HTTPException19 } from "hono/http-exception";
|
|
9200
9264
|
function openGeniSlackBotMetadata(metadata) {
|
|
9201
9265
|
const parsed = OpenGeniSlackBotConnectionMetadata.safeParse(metadata);
|
|
9202
9266
|
return parsed.success ? parsed.data : null;
|
|
@@ -9215,12 +9279,12 @@ function hasReservedOpenGeniSlackBotSessionMetadata(metadata) {
|
|
|
9215
9279
|
async function requireOpenGeniSlackBotConnection(db, workspaceId, connectionId) {
|
|
9216
9280
|
const connection = await getConnectionMetadata3(db, workspaceId, connectionId, null);
|
|
9217
9281
|
if (!connection || !isOpenGeniSlackBotConnection(connection)) {
|
|
9218
|
-
throw new
|
|
9282
|
+
throw new HTTPException19(422, {
|
|
9219
9283
|
message: "slackBotConnectionId must reference an OpenGeni Slack bot connection"
|
|
9220
9284
|
});
|
|
9221
9285
|
}
|
|
9222
9286
|
if (connection.status !== "active") {
|
|
9223
|
-
throw new
|
|
9287
|
+
throw new HTTPException19(422, {
|
|
9224
9288
|
message: `OpenGeni Slack bot connection is not active (${connection.status})`
|
|
9225
9289
|
});
|
|
9226
9290
|
}
|
|
@@ -9564,25 +9628,25 @@ var sessionMcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
|
9564
9628
|
async function requireAtomicPersonalResourceAttachment(deps, authorization, workspaceId, intent, existingSession) {
|
|
9565
9629
|
if (!intent) return;
|
|
9566
9630
|
if (!authorization) {
|
|
9567
|
-
throw new
|
|
9631
|
+
throw new HTTPException20(403, {
|
|
9568
9632
|
message: "Personal resources require the owning managed-human session."
|
|
9569
9633
|
});
|
|
9570
9634
|
}
|
|
9571
9635
|
try {
|
|
9572
9636
|
requireVerifiedOwningUser(authorization, workspaceId);
|
|
9573
9637
|
} catch (error) {
|
|
9574
|
-
throw new
|
|
9638
|
+
throw new HTTPException20(403, {
|
|
9575
9639
|
message: "Personal resources require the owning managed-human session.",
|
|
9576
9640
|
cause: error
|
|
9577
9641
|
});
|
|
9578
9642
|
}
|
|
9579
9643
|
if (!await sessionTenancyProductActivated2(deps.db, workspaceId)) {
|
|
9580
|
-
throw new
|
|
9644
|
+
throw new HTTPException20(409, {
|
|
9581
9645
|
message: "Session tenancy is not activated for this organization."
|
|
9582
9646
|
});
|
|
9583
9647
|
}
|
|
9584
9648
|
if (existingSession && intent.expectedAuthorityEpoch === void 0) {
|
|
9585
|
-
throw new
|
|
9649
|
+
throw new HTTPException20(422, {
|
|
9586
9650
|
message: "Personal-resource attachment requires expectedAuthorityEpoch."
|
|
9587
9651
|
});
|
|
9588
9652
|
}
|
|
@@ -9610,7 +9674,7 @@ function resolveFirstPartyMcpToolsForCreate(requested, parentStored, policy = {
|
|
|
9610
9674
|
const parentCeiling = effectiveFirstPartyMcpToolCeiling(parentStored, policy);
|
|
9611
9675
|
const widened = requested.find((tool) => !parentCeiling.has(tool));
|
|
9612
9676
|
if (widened) {
|
|
9613
|
-
throw new
|
|
9677
|
+
throw new HTTPException20(403, {
|
|
9614
9678
|
message: `child first-party MCP tools may only narrow the parent session selection: ${widened}`
|
|
9615
9679
|
});
|
|
9616
9680
|
}
|
|
@@ -9639,7 +9703,7 @@ function sessionSpawnDenialEnvelope(error) {
|
|
|
9639
9703
|
function serviceInitiatorForGrant(grant) {
|
|
9640
9704
|
if (!grant.serviceInitiator) {
|
|
9641
9705
|
if (grant.serviceInitiatorContext) {
|
|
9642
|
-
throw new
|
|
9706
|
+
throw new HTTPException20(403, {
|
|
9643
9707
|
message: "service initiator context requires a signed service initiator"
|
|
9644
9708
|
});
|
|
9645
9709
|
}
|
|
@@ -9647,13 +9711,13 @@ function serviceInitiatorForGrant(grant) {
|
|
|
9647
9711
|
}
|
|
9648
9712
|
const initiator = ServiceTurnInitiator.safeParse(grant.serviceInitiator);
|
|
9649
9713
|
if (!initiator.success) {
|
|
9650
|
-
throw new
|
|
9714
|
+
throw new HTTPException20(403, {
|
|
9651
9715
|
message: "a delegated command initiator must be a bounded service principal"
|
|
9652
9716
|
});
|
|
9653
9717
|
}
|
|
9654
9718
|
const context = ServiceTurnInitiatorContext.safeParse(grant.serviceInitiatorContext ?? {});
|
|
9655
9719
|
if (!context.success) {
|
|
9656
|
-
throw new
|
|
9720
|
+
throw new HTTPException20(403, {
|
|
9657
9721
|
message: "delegated service initiator context is invalid or reserved"
|
|
9658
9722
|
});
|
|
9659
9723
|
}
|
|
@@ -9661,7 +9725,7 @@ function serviceInitiatorForGrant(grant) {
|
|
|
9661
9725
|
const callerAttemptId = grant.metadata?.["attemptId"];
|
|
9662
9726
|
const callerExecutionGeneration = grant.metadata?.["executionGeneration"];
|
|
9663
9727
|
if (callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0) {
|
|
9664
|
-
throw new
|
|
9728
|
+
throw new HTTPException20(403, {
|
|
9665
9729
|
message: "a service initiator cannot replace an exact agent-attempt initiator"
|
|
9666
9730
|
});
|
|
9667
9731
|
}
|
|
@@ -9679,7 +9743,7 @@ function creationInitiatorForGrant(grant) {
|
|
|
9679
9743
|
const hasCallerTurnClaim = callerTurnId !== void 0 || callerAttemptId !== void 0 || callerExecutionGeneration !== void 0;
|
|
9680
9744
|
if (hasCallerTurnClaim) {
|
|
9681
9745
|
if (typeof callerSessionId !== "string" || typeof callerTurnId !== "string" || typeof callerAttemptId !== "string" || typeof callerExecutionGeneration !== "number" || !Number.isSafeInteger(callerExecutionGeneration) || callerExecutionGeneration < 1) {
|
|
9682
|
-
throw new
|
|
9746
|
+
throw new HTTPException20(403, {
|
|
9683
9747
|
message: "caller attempt claims are incomplete"
|
|
9684
9748
|
});
|
|
9685
9749
|
}
|
|
@@ -9710,31 +9774,31 @@ function normalizedSessionMcpCredentialHeaders(headers) {
|
|
|
9710
9774
|
}
|
|
9711
9775
|
const entries = Object.entries(headers).map(([name, value]) => [name.trim(), value]).filter(([name]) => name.length > 0);
|
|
9712
9776
|
if (entries.length > maxSessionMcpCredentialHeaders) {
|
|
9713
|
-
throw new
|
|
9777
|
+
throw new HTTPException20(422, {
|
|
9714
9778
|
message: `a session MCP server supports at most ${maxSessionMcpCredentialHeaders} credential headers`
|
|
9715
9779
|
});
|
|
9716
9780
|
}
|
|
9717
9781
|
const seen = /* @__PURE__ */ new Set();
|
|
9718
9782
|
for (const [name, value] of entries) {
|
|
9719
9783
|
if (!sessionMcpCredentialHeaderName.test(name)) {
|
|
9720
|
-
throw new
|
|
9784
|
+
throw new HTTPException20(422, {
|
|
9721
9785
|
message: `invalid credential header name: ${name}`
|
|
9722
9786
|
});
|
|
9723
9787
|
}
|
|
9724
9788
|
const lower = name.toLowerCase();
|
|
9725
9789
|
if (seen.has(lower)) {
|
|
9726
|
-
throw new
|
|
9790
|
+
throw new HTTPException20(422, {
|
|
9727
9791
|
message: `duplicate credential header name: ${name}`
|
|
9728
9792
|
});
|
|
9729
9793
|
}
|
|
9730
9794
|
seen.add(lower);
|
|
9731
9795
|
if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
|
|
9732
|
-
throw new
|
|
9796
|
+
throw new HTTPException20(422, {
|
|
9733
9797
|
message: `credential header ${name} must be 1-${maxSessionMcpCredentialHeaderValueLength} characters`
|
|
9734
9798
|
});
|
|
9735
9799
|
}
|
|
9736
9800
|
if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
|
|
9737
|
-
throw new
|
|
9801
|
+
throw new HTTPException20(422, {
|
|
9738
9802
|
message: `credential header ${name} contains forbidden control characters`
|
|
9739
9803
|
});
|
|
9740
9804
|
}
|
|
@@ -9802,13 +9866,13 @@ function validateSessionMcpServersForCreate(settings, grant, servers) {
|
|
|
9802
9866
|
for (const server of servers) {
|
|
9803
9867
|
assertHostMcpAuthoritySourceAdmissionEnabled(settings, server.connectionRef);
|
|
9804
9868
|
if (seenIds.has(server.id)) {
|
|
9805
|
-
throw new
|
|
9869
|
+
throw new HTTPException20(422, {
|
|
9806
9870
|
message: `duplicate session MCP server id: ${server.id}`
|
|
9807
9871
|
});
|
|
9808
9872
|
}
|
|
9809
9873
|
seenIds.add(server.id);
|
|
9810
9874
|
if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
|
|
9811
|
-
throw new
|
|
9875
|
+
throw new HTTPException20(422, {
|
|
9812
9876
|
message: `MCP server id already exists: ${server.id}`
|
|
9813
9877
|
});
|
|
9814
9878
|
}
|
|
@@ -9850,13 +9914,13 @@ function validateInheritedSessionMcpServersForCreate(servers) {
|
|
|
9850
9914
|
const seenIds = /* @__PURE__ */ new Set();
|
|
9851
9915
|
for (const server of servers) {
|
|
9852
9916
|
if (seenIds.has(server.id)) {
|
|
9853
|
-
throw new
|
|
9917
|
+
throw new HTTPException20(422, {
|
|
9854
9918
|
message: `duplicate inherited session MCP server id: ${server.id}`
|
|
9855
9919
|
});
|
|
9856
9920
|
}
|
|
9857
9921
|
seenIds.add(server.id);
|
|
9858
9922
|
if (reservedSessionMcpServerIds.has(server.id)) {
|
|
9859
|
-
throw new
|
|
9923
|
+
throw new HTTPException20(422, {
|
|
9860
9924
|
message: `reserved inherited session MCP server id: ${server.id}`
|
|
9861
9925
|
});
|
|
9862
9926
|
}
|
|
@@ -9888,13 +9952,13 @@ function validateSessionMcpCredentialUpdates(input) {
|
|
|
9888
9952
|
const seenIds = /* @__PURE__ */ new Set();
|
|
9889
9953
|
const encryptedUpdates = input.updates.map((update) => {
|
|
9890
9954
|
if (seenIds.has(update.id)) {
|
|
9891
|
-
throw new
|
|
9955
|
+
throw new HTTPException20(422, {
|
|
9892
9956
|
message: `duplicate session MCP credential update id: ${update.id}`
|
|
9893
9957
|
});
|
|
9894
9958
|
}
|
|
9895
9959
|
seenIds.add(update.id);
|
|
9896
9960
|
if (!knownIds.has(update.id)) {
|
|
9897
|
-
throw new
|
|
9961
|
+
throw new HTTPException20(422, {
|
|
9898
9962
|
message: `unknown session MCP server id: ${update.id}`
|
|
9899
9963
|
});
|
|
9900
9964
|
}
|
|
@@ -9961,7 +10025,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
9961
10025
|
let targetSeededBeforeCreateCommit = false;
|
|
9962
10026
|
const beforeCreateCommit = requiresActiveWorkspaceCustomModel || input.consumeNewSessionDraft || input.beforeCreateCommit || preflightTarget || targetPreflightFailureMessage ? async (tx, sessionId, context) => {
|
|
9963
10027
|
if (targetPreflightFailureMessage && context?.created !== false) {
|
|
9964
|
-
throw new
|
|
10028
|
+
throw new HTTPException20(422, {
|
|
9965
10029
|
message: targetPreflightFailureMessage
|
|
9966
10030
|
});
|
|
9967
10031
|
}
|
|
@@ -9977,7 +10041,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
9977
10041
|
reference
|
|
9978
10042
|
});
|
|
9979
10043
|
if (!active) {
|
|
9980
|
-
throw new
|
|
10044
|
+
throw new HTTPException20(422, {
|
|
9981
10045
|
message: `model is not available: ${input.model}`
|
|
9982
10046
|
});
|
|
9983
10047
|
}
|
|
@@ -10002,7 +10066,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
10002
10066
|
workingDir: preflightTarget.workingDir
|
|
10003
10067
|
});
|
|
10004
10068
|
if (!seeded.swapped) {
|
|
10005
|
-
throw new
|
|
10069
|
+
throw new HTTPException20(422, {
|
|
10006
10070
|
message: `cannot target sandbox ${seedTargetForNewSession.sandboxId}: target authority changed during session creation`
|
|
10007
10071
|
});
|
|
10008
10072
|
}
|
|
@@ -10187,7 +10251,7 @@ async function finishStartSession(input, session) {
|
|
|
10187
10251
|
input.seedTargetSandbox.workingDir ?? null
|
|
10188
10252
|
);
|
|
10189
10253
|
if (!seeded.swapped) {
|
|
10190
|
-
throw new
|
|
10254
|
+
throw new HTTPException20(422, {
|
|
10191
10255
|
message: `cannot target sandbox ${input.seedTargetSandbox.sandboxId}: ${seeded.reason ?? "target is not attachable"}`
|
|
10192
10256
|
});
|
|
10193
10257
|
}
|
|
@@ -10262,7 +10326,7 @@ function canonicalConfiguredModel(settings, model) {
|
|
|
10262
10326
|
if (settings.supergrokSubscriptionEnabled && canonicalModel.startsWith(XAI_SUBSCRIPTION_MODEL_ID_PREFIX)) {
|
|
10263
10327
|
return canonicalModel;
|
|
10264
10328
|
}
|
|
10265
|
-
throw new
|
|
10329
|
+
throw new HTTPException20(422, { message: `model is not available: ${model}` });
|
|
10266
10330
|
}
|
|
10267
10331
|
function assertConfiguredModel(settings, model) {
|
|
10268
10332
|
canonicalConfiguredModel(settings, model);
|
|
@@ -10303,7 +10367,7 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
|
|
|
10303
10367
|
modelId: canonicalModel
|
|
10304
10368
|
});
|
|
10305
10369
|
if (!verdict.allowed) {
|
|
10306
|
-
throw new
|
|
10370
|
+
throw new HTTPException20(422, {
|
|
10307
10371
|
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`
|
|
10308
10372
|
});
|
|
10309
10373
|
}
|
|
@@ -10311,10 +10375,10 @@ async function assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model
|
|
|
10311
10375
|
async function requireQueuedTurnForApi(db, workspaceId, sessionId, turnId) {
|
|
10312
10376
|
const turn = await getSessionTurn(db, workspaceId, turnId);
|
|
10313
10377
|
if (!turn || turn.sessionId !== sessionId) {
|
|
10314
|
-
throw new
|
|
10378
|
+
throw new HTTPException20(404, { message: "session turn not found" });
|
|
10315
10379
|
}
|
|
10316
10380
|
if (turn.status !== "queued") {
|
|
10317
|
-
throw new
|
|
10381
|
+
throw new HTTPException20(409, {
|
|
10318
10382
|
message: `turn is ${turn.status}; only queued turns can be changed`
|
|
10319
10383
|
});
|
|
10320
10384
|
}
|
|
@@ -10425,7 +10489,7 @@ async function postUserMessageTurn(input) {
|
|
|
10425
10489
|
assertSessionAllowsProductModel(sessionForModelGate, effectiveModelForGate);
|
|
10426
10490
|
} catch (error) {
|
|
10427
10491
|
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
10428
|
-
throw new
|
|
10492
|
+
throw new HTTPException20(422, { message: error.message, cause: error });
|
|
10429
10493
|
}
|
|
10430
10494
|
throw error;
|
|
10431
10495
|
}
|
|
@@ -10491,7 +10555,7 @@ async function postUserMessageTurn(input) {
|
|
|
10491
10555
|
reference
|
|
10492
10556
|
});
|
|
10493
10557
|
if (!active) {
|
|
10494
|
-
throw new
|
|
10558
|
+
throw new HTTPException20(422, {
|
|
10495
10559
|
message: `model is not available: ${freshWorkspaceCustomModel}`
|
|
10496
10560
|
});
|
|
10497
10561
|
}
|
|
@@ -10506,19 +10570,19 @@ async function postUserMessageTurn(input) {
|
|
|
10506
10570
|
throw error;
|
|
10507
10571
|
}
|
|
10508
10572
|
if (error instanceof PersonalResourceAttachmentAcceptanceError) {
|
|
10509
|
-
throw new
|
|
10573
|
+
throw new HTTPException20(
|
|
10510
10574
|
error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
|
|
10511
10575
|
{ message: error.message, cause: error }
|
|
10512
10576
|
);
|
|
10513
10577
|
}
|
|
10514
10578
|
if (error instanceof QueueCommandConflictError || error instanceof SessionControlConflictError) {
|
|
10515
|
-
throw new
|
|
10579
|
+
throw new HTTPException20(409, { message: error.message });
|
|
10516
10580
|
}
|
|
10517
10581
|
if (error instanceof Error && error.message.includes("cancelled")) {
|
|
10518
|
-
throw new
|
|
10582
|
+
throw new HTTPException20(409, { message: error.message });
|
|
10519
10583
|
}
|
|
10520
10584
|
if (error instanceof Error && error.message.startsWith("Unknown session MCP server")) {
|
|
10521
|
-
throw new
|
|
10585
|
+
throw new HTTPException20(422, { message: error.message });
|
|
10522
10586
|
}
|
|
10523
10587
|
throw error;
|
|
10524
10588
|
}
|
|
@@ -10574,28 +10638,28 @@ function resolveSessionCreateScope(input) {
|
|
|
10574
10638
|
} else {
|
|
10575
10639
|
const agentAccess = requested.agentAccessProvided ? requested.agentAccess : parent.agentAccess;
|
|
10576
10640
|
if (AGENT_ACCESS_WIDTH[agentAccess] > AGENT_ACCESS_WIDTH[parent.agentAccess]) {
|
|
10577
|
-
throw new
|
|
10641
|
+
throw new HTTPException20(403, {
|
|
10578
10642
|
message: "child agent access may only narrow the parent session"
|
|
10579
10643
|
});
|
|
10580
10644
|
}
|
|
10581
10645
|
if (requested.endUserProvided) {
|
|
10582
10646
|
const same = requested.scopeSubjectId !== null && parent.scopeSubjectId !== null && requested.scopeSubjectId === parent.scopeSubjectId;
|
|
10583
10647
|
if (!same) {
|
|
10584
|
-
throw new
|
|
10648
|
+
throw new HTTPException20(403, {
|
|
10585
10649
|
message: "child end-user label must equal the parent session label"
|
|
10586
10650
|
});
|
|
10587
10651
|
}
|
|
10588
10652
|
}
|
|
10589
10653
|
const memoryScope = requested.memoryScopeProvided ? requested.memoryScope : parent.memoryScope;
|
|
10590
10654
|
if (MEMORY_SCOPE_WIDTH[memoryScope] > MEMORY_SCOPE_WIDTH[parent.memoryScope]) {
|
|
10591
|
-
throw new
|
|
10655
|
+
throw new HTTPException20(403, {
|
|
10592
10656
|
message: "child memory scope may only narrow the parent session"
|
|
10593
10657
|
});
|
|
10594
10658
|
}
|
|
10595
10659
|
resolved = { agentAccess, scopeSubjectId: parent.scopeSubjectId, memoryScope };
|
|
10596
10660
|
}
|
|
10597
10661
|
if (resolved.memoryScope === "user" && resolved.scopeSubjectId === null) {
|
|
10598
|
-
throw new
|
|
10662
|
+
throw new HTTPException20(422, { message: 'memoryScope "user" requires an authenticated user' });
|
|
10599
10663
|
}
|
|
10600
10664
|
return resolved;
|
|
10601
10665
|
}
|
|
@@ -10645,14 +10709,14 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10645
10709
|
const externalBeforeCreateCommit = externalContinuationCommitAuthorizer(authorization);
|
|
10646
10710
|
const hostSelections = payload.selectedHostMcpDelegations ?? [];
|
|
10647
10711
|
if (hostSelections.length && (!authorization || authorization.grant.accountId !== grant.accountId || authorization.grant.subjectId !== grant.subjectId || authorization?.grant.workspaceId !== workspaceId || !hasPermission(authorization?.grant.permissions ?? [], "connections:read") || !hasPermission(grant.permissions, "connections:read") || grant.metadata?.["sessionId"] || payload.startMode === "realtime")) {
|
|
10648
|
-
throw new
|
|
10712
|
+
throw new HTTPException20(403, {
|
|
10649
10713
|
message: "Host selection requires a verified direct owner and non-realtime start"
|
|
10650
10714
|
});
|
|
10651
10715
|
}
|
|
10652
10716
|
if (hostSelections.length)
|
|
10653
10717
|
prepareHostMcpOwnerAuthorization(authorization, workspaceId, "connections:read");
|
|
10654
10718
|
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
10655
|
-
throw new
|
|
10719
|
+
throw new HTTPException20(422, {
|
|
10656
10720
|
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2} is reserved for scheduler routing`
|
|
10657
10721
|
});
|
|
10658
10722
|
}
|
|
@@ -10660,13 +10724,13 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10660
10724
|
const visibilityProvided = hasOwnProperty(rawPayload, "visibility");
|
|
10661
10725
|
if (payload.visibility === "private" && !grant.metadata?.["sessionId"]) {
|
|
10662
10726
|
if (!authorization) {
|
|
10663
|
-
throw new
|
|
10727
|
+
throw new HTTPException20(403, {
|
|
10664
10728
|
message: "managed human session required"
|
|
10665
10729
|
});
|
|
10666
10730
|
}
|
|
10667
10731
|
await requireManagedHumanPrivateSessionCreate(unresolvedDeps, authorization, workspaceId);
|
|
10668
10732
|
if (payload.sandbox === "shared" || typeof payload.sandbox === "object") {
|
|
10669
|
-
throw new
|
|
10733
|
+
throw new HTTPException20(422, {
|
|
10670
10734
|
message: "Only-me sessions require their own sandbox"
|
|
10671
10735
|
});
|
|
10672
10736
|
}
|
|
@@ -10681,14 +10745,14 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10681
10745
|
});
|
|
10682
10746
|
} catch (error) {
|
|
10683
10747
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
10684
|
-
throw new
|
|
10748
|
+
throw new HTTPException20(403, { message: error.message, cause: error });
|
|
10685
10749
|
}
|
|
10686
10750
|
throw error;
|
|
10687
10751
|
}
|
|
10688
10752
|
}
|
|
10689
10753
|
const parentSession = parentSessionId ? await getSession2(db, workspaceId, parentSessionId) : null;
|
|
10690
10754
|
if (parentSessionId && !parentSession) {
|
|
10691
|
-
throw new
|
|
10755
|
+
throw new HTTPException20(404, {
|
|
10692
10756
|
message: `parent session not found in workspace: ${parentSessionId}`
|
|
10693
10757
|
});
|
|
10694
10758
|
}
|
|
@@ -10696,7 +10760,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10696
10760
|
const workspaceSessionToolDefaults = resolveWorkspaceSessionToolDefaults2(workspace.settings);
|
|
10697
10761
|
const parentAuthority = parentSession ? await getSessionAuthorityProjection2(db, workspaceId, parentSession.id) : null;
|
|
10698
10762
|
if (parentSession && !parentAuthority) {
|
|
10699
|
-
throw new
|
|
10763
|
+
throw new HTTPException20(403, {
|
|
10700
10764
|
message: "parent session authority is unavailable"
|
|
10701
10765
|
});
|
|
10702
10766
|
}
|
|
@@ -10708,7 +10772,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10708
10772
|
parentVisibility: parentAuthority?.visibility ?? null
|
|
10709
10773
|
});
|
|
10710
10774
|
} catch (error) {
|
|
10711
|
-
throw new
|
|
10775
|
+
throw new HTTPException20(422, {
|
|
10712
10776
|
message: error instanceof Error ? error.message : "invalid child visibility"
|
|
10713
10777
|
});
|
|
10714
10778
|
}
|
|
@@ -10719,7 +10783,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10719
10783
|
parentSession?.bundledSkillIds
|
|
10720
10784
|
);
|
|
10721
10785
|
} catch (error) {
|
|
10722
|
-
throw new
|
|
10786
|
+
throw new HTTPException20(422, {
|
|
10723
10787
|
message: error instanceof Error ? error.message : "Invalid bundled Skill selection"
|
|
10724
10788
|
});
|
|
10725
10789
|
}
|
|
@@ -10747,7 +10811,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10747
10811
|
creationInitiator.actor.attemptId
|
|
10748
10812
|
) : null;
|
|
10749
10813
|
if (creationInitiator.actor && (!parentCallingTurn || parentCallingTurn.sessionId !== parentSession?.id)) {
|
|
10750
|
-
throw new
|
|
10814
|
+
throw new HTTPException20(403, {
|
|
10751
10815
|
message: "caller attempt does not belong to the parent session"
|
|
10752
10816
|
});
|
|
10753
10817
|
}
|
|
@@ -10803,12 +10867,12 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10803
10867
|
}
|
|
10804
10868
|
} catch (error) {
|
|
10805
10869
|
if (error instanceof SessionIdConflictError) {
|
|
10806
|
-
throw new
|
|
10870
|
+
throw new HTTPException20(409, {
|
|
10807
10871
|
message: "requested session id is already in use"
|
|
10808
10872
|
});
|
|
10809
10873
|
}
|
|
10810
10874
|
if (error instanceof SessionCreateIdempotencyConflictError) {
|
|
10811
|
-
throw new
|
|
10875
|
+
throw new HTTPException20(409, { message: error.message, cause: error });
|
|
10812
10876
|
}
|
|
10813
10877
|
throw error;
|
|
10814
10878
|
}
|
|
@@ -10861,7 +10925,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10861
10925
|
}
|
|
10862
10926
|
);
|
|
10863
10927
|
} catch (error) {
|
|
10864
|
-
throw new
|
|
10928
|
+
throw new HTTPException20(422, {
|
|
10865
10929
|
message: error instanceof Error ? error.message : "invalid child goal root constraints"
|
|
10866
10930
|
});
|
|
10867
10931
|
}
|
|
@@ -10914,12 +10978,12 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10914
10978
|
for (const capabilityId of selectedInstalledSkillIds) {
|
|
10915
10979
|
const installed = installedById.get(capabilityId);
|
|
10916
10980
|
if (!installed) {
|
|
10917
|
-
throw new
|
|
10981
|
+
throw new HTTPException20(422, {
|
|
10918
10982
|
message: `Session-selected Skill is not installed in this workspace: ${capabilityId}`
|
|
10919
10983
|
});
|
|
10920
10984
|
}
|
|
10921
10985
|
if (installed.activationMode !== "session_selected") {
|
|
10922
|
-
throw new
|
|
10986
|
+
throw new HTTPException20(422, {
|
|
10923
10987
|
message: `Installed Skill does not require explicit session selection: ${capabilityId}`
|
|
10924
10988
|
});
|
|
10925
10989
|
}
|
|
@@ -10934,7 +10998,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10934
10998
|
try {
|
|
10935
10999
|
skills = SessionSkills.parse([...inheritedOrSubmittedSkills, ...selectedInstalledSkills]);
|
|
10936
11000
|
} catch (error) {
|
|
10937
|
-
throw new
|
|
11001
|
+
throw new HTTPException20(422, {
|
|
10938
11002
|
message: error instanceof Error ? error.message : "invalid session Skill selection"
|
|
10939
11003
|
});
|
|
10940
11004
|
}
|
|
@@ -10998,7 +11062,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10998
11062
|
const captureLinkedAuthority = prepareExternalLinkTurnAdmission(authorization);
|
|
10999
11063
|
await validateGitHubRepositorySelection(db, workspaceId, resources);
|
|
11000
11064
|
if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
11001
|
-
throw new
|
|
11065
|
+
throw new HTTPException20(503, {
|
|
11002
11066
|
message: "object storage is not configured"
|
|
11003
11067
|
});
|
|
11004
11068
|
}
|
|
@@ -11016,7 +11080,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11016
11080
|
const rig = await getRig4(db, grant, requestedRigId);
|
|
11017
11081
|
if (!rig || !rig.activeVersion) {
|
|
11018
11082
|
if (payload.rigId) {
|
|
11019
|
-
throw new
|
|
11083
|
+
throw new HTTPException20(422, {
|
|
11020
11084
|
message: rig ? `rig ${payload.rigId} has no active version to bind` : `unknown rigId: ${payload.rigId}`
|
|
11021
11085
|
});
|
|
11022
11086
|
}
|
|
@@ -11037,7 +11101,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11037
11101
|
if (payload.channelId) {
|
|
11038
11102
|
const channel = await getChannel(db, workspaceId, payload.channelId);
|
|
11039
11103
|
if (!channel) {
|
|
11040
|
-
throw new
|
|
11104
|
+
throw new HTTPException20(422, {
|
|
11041
11105
|
message: `unknown channelId: ${payload.channelId}`
|
|
11042
11106
|
});
|
|
11043
11107
|
}
|
|
@@ -11053,7 +11117,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11053
11117
|
const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
|
|
11054
11118
|
const latencyMode = payload.latencyMode ?? inheritedLatencyMode;
|
|
11055
11119
|
if (payload.expectedNewSessionDraftRevision !== void 0 && payload.rigId === null) {
|
|
11056
|
-
throw new
|
|
11120
|
+
throw new HTTPException20(409, {
|
|
11057
11121
|
message: "The submitted session options are not represented by the new-session draft"
|
|
11058
11122
|
});
|
|
11059
11123
|
}
|
|
@@ -11094,7 +11158,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11094
11158
|
if (parentFirstPartyMcpPermissions && payload.firstPartyMcpPermissions?.some(
|
|
11095
11159
|
(permission) => !hasPermission(parentFirstPartyMcpPermissions, permission)
|
|
11096
11160
|
)) {
|
|
11097
|
-
throw new
|
|
11161
|
+
throw new HTTPException20(403, {
|
|
11098
11162
|
message: "child first-party MCP permissions may only narrow the parent session grant"
|
|
11099
11163
|
});
|
|
11100
11164
|
}
|
|
@@ -11102,19 +11166,19 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11102
11166
|
(permission) => hasPermission(grant.permissions, permission)
|
|
11103
11167
|
) : null);
|
|
11104
11168
|
if (firstPartyMcpPermissions && firstPartyMcpPermissions.length === 0) {
|
|
11105
|
-
throw new
|
|
11169
|
+
throw new HTTPException20(422, {
|
|
11106
11170
|
message: "firstPartyMcpPermissions must not be empty; omit it for the default worker permission set"
|
|
11107
11171
|
});
|
|
11108
11172
|
}
|
|
11109
11173
|
for (const permission of firstPartyMcpPermissions ?? []) {
|
|
11110
11174
|
if (!hasPermission(grant.permissions, permission)) {
|
|
11111
|
-
throw new
|
|
11175
|
+
throw new HTTPException20(403, {
|
|
11112
11176
|
message: `cannot grant first-party MCP permission beyond the creating grant: ${permission}`
|
|
11113
11177
|
});
|
|
11114
11178
|
}
|
|
11115
11179
|
}
|
|
11116
11180
|
if (effectiveGoal && firstPartyMcpPermissions && !firstPartyMcpPermissions.includes("goals:manage")) {
|
|
11117
|
-
throw new
|
|
11181
|
+
throw new HTTPException20(422, {
|
|
11118
11182
|
message: "goal-bearing sessions require goals:manage in the resulting first-party MCP permission set"
|
|
11119
11183
|
});
|
|
11120
11184
|
}
|
|
@@ -11123,7 +11187,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11123
11187
|
(tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool)
|
|
11124
11188
|
);
|
|
11125
11189
|
if (disallowedFirstPartyMcpTool) {
|
|
11126
|
-
throw new
|
|
11190
|
+
throw new HTTPException20(422, {
|
|
11127
11191
|
message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`
|
|
11128
11192
|
});
|
|
11129
11193
|
}
|
|
@@ -11156,13 +11220,13 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11156
11220
|
(name) => !firstPartyMcpTools.includes(name)
|
|
11157
11221
|
);
|
|
11158
11222
|
if (missingGoalTools.length > 0) {
|
|
11159
|
-
throw new
|
|
11223
|
+
throw new HTTPException20(422, {
|
|
11160
11224
|
message: `goal-bearing sessions require first-party MCP tools: ${missingGoalTools.join(", ")}`
|
|
11161
11225
|
});
|
|
11162
11226
|
}
|
|
11163
11227
|
}
|
|
11164
11228
|
if (payload.targetSandboxId && payload.sandbox !== void 0 && payload.sandbox !== "new") {
|
|
11165
|
-
throw new
|
|
11229
|
+
throw new HTTPException20(422, {
|
|
11166
11230
|
message: "targetSandboxId requires an own sandbox (omit sandbox or pass 'new'); it cannot join a shared group"
|
|
11167
11231
|
});
|
|
11168
11232
|
}
|
|
@@ -11176,7 +11240,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11176
11240
|
const rigVersionMatchesGroup = (memberRigVersionId) => memberRigVersionId === frozenRigVersionId;
|
|
11177
11241
|
if (sandboxChoice === "shared") {
|
|
11178
11242
|
if (!parentSessionId) {
|
|
11179
|
-
throw new
|
|
11243
|
+
throw new HTTPException20(422, {
|
|
11180
11244
|
message: "sandbox:'shared' requires a parent session (spawn from inside a session); use 'new' for a top-level create."
|
|
11181
11245
|
});
|
|
11182
11246
|
}
|
|
@@ -11199,7 +11263,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11199
11263
|
}
|
|
11200
11264
|
if (variableSetMismatch || rigMismatch) {
|
|
11201
11265
|
if (payload.sandbox === "shared") {
|
|
11202
|
-
throw new
|
|
11266
|
+
throw new HTTPException20(422, {
|
|
11203
11267
|
message: variableSetMismatch ? "sandbox:'shared' requires the same variableSet / same environment as the creator's box (the box variable set/environment is fixed at creation); omit sandbox or pass 'new' when attaching a different variableSet/environment." : "sandbox:'shared' requires the same rig as the creator's box (the box's rig setup is fixed at creation); omit sandbox or pass 'new' when binding a different rig."
|
|
11204
11268
|
});
|
|
11205
11269
|
}
|
|
@@ -11215,7 +11279,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11215
11279
|
} else if (typeof sandboxChoice === "object") {
|
|
11216
11280
|
const member = await getAnySessionInGroup(db, workspaceId, sandboxChoice.groupId);
|
|
11217
11281
|
if (!member) {
|
|
11218
|
-
throw new
|
|
11282
|
+
throw new HTTPException20(404, {
|
|
11219
11283
|
message: `sandbox group not found in workspace: ${sandboxChoice.groupId}`
|
|
11220
11284
|
});
|
|
11221
11285
|
}
|
|
@@ -11228,7 +11292,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11228
11292
|
if (!memberVariableSetSelections.every(
|
|
11229
11293
|
(memberVariableSetIds) => variableSetsMatchGroup(memberVariableSetIds)
|
|
11230
11294
|
)) {
|
|
11231
|
-
throw new
|
|
11295
|
+
throw new HTTPException20(422, {
|
|
11232
11296
|
message: `sandbox group ${sandboxChoice.groupId} runs a different variableSet / different environment (the box variable set/environment is fixed at creation); create with the group's variableSet/environment or omit sandbox for an own box.`
|
|
11233
11297
|
});
|
|
11234
11298
|
}
|
|
@@ -11240,7 +11304,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11240
11304
|
if (!memberRigVersionIds.every(
|
|
11241
11305
|
(memberRigVersionId) => rigVersionMatchesGroup(memberRigVersionId)
|
|
11242
11306
|
)) {
|
|
11243
|
-
throw new
|
|
11307
|
+
throw new HTTPException20(422, {
|
|
11244
11308
|
message: `sandbox group ${sandboxChoice.groupId} runs a different rig (the box's rig setup is fixed at creation); create with the group's rig or omit sandbox for an own box.`
|
|
11245
11309
|
});
|
|
11246
11310
|
}
|
|
@@ -11250,12 +11314,12 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11250
11314
|
inheritedSandboxOs = member.sandboxOs;
|
|
11251
11315
|
}
|
|
11252
11316
|
if (payload.workingDir !== void 0 && !payload.targetSandboxId) {
|
|
11253
|
-
throw new
|
|
11317
|
+
throw new HTTPException20(422, {
|
|
11254
11318
|
message: "workingDir requires targetSandboxId (it is the targeted machine's working directory)"
|
|
11255
11319
|
});
|
|
11256
11320
|
}
|
|
11257
11321
|
if (inheritedBackend === void 0 && !payload.targetSandboxId && (payload.sandboxBackend ?? settings.sandboxBackend) === "selfhosted") {
|
|
11258
|
-
throw new
|
|
11322
|
+
throw new HTTPException20(422, {
|
|
11259
11323
|
message: "selfhosted sessions require targetSandboxId; select an online Connected Machine"
|
|
11260
11324
|
});
|
|
11261
11325
|
}
|
|
@@ -11288,7 +11352,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11288
11352
|
workingDir: payload.workingDir ?? null
|
|
11289
11353
|
} : inheritedActiveTarget;
|
|
11290
11354
|
if (effectiveSandboxBackend === "selfhosted" && effectiveSeedTarget === null && managedSessionGroupBackend(settings.sandboxBackend, effectiveSandboxBackend) === null) {
|
|
11291
|
-
throw new
|
|
11355
|
+
throw new HTTPException20(422, {
|
|
11292
11356
|
message: "self-hosted execution runs on a Connected Machine, but no machine was selected or inherited; connect the parent session to a machine or provide machineTarget"
|
|
11293
11357
|
});
|
|
11294
11358
|
}
|
|
@@ -11411,39 +11475,39 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
11411
11475
|
});
|
|
11412
11476
|
} catch (error) {
|
|
11413
11477
|
if (error instanceof PersonalResourceAttachmentAcceptanceError) {
|
|
11414
|
-
throw new
|
|
11478
|
+
throw new HTTPException20(
|
|
11415
11479
|
error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
|
|
11416
11480
|
{ message: error.message, cause: error }
|
|
11417
11481
|
);
|
|
11418
11482
|
}
|
|
11419
11483
|
if (error instanceof HostMcpDelegationAuthorityError2) {
|
|
11420
|
-
throw new
|
|
11484
|
+
throw new HTTPException20(403, {
|
|
11421
11485
|
message: "Host delegation authority unavailable",
|
|
11422
11486
|
cause: error
|
|
11423
11487
|
});
|
|
11424
11488
|
}
|
|
11425
11489
|
if (error instanceof HostMcpBindingConflictError) {
|
|
11426
|
-
throw new
|
|
11490
|
+
throw new HTTPException20(409, {
|
|
11427
11491
|
message: "Host delegation selection conflicts",
|
|
11428
11492
|
cause: error
|
|
11429
11493
|
});
|
|
11430
11494
|
}
|
|
11431
11495
|
if (error instanceof AgentCommandAuthorityError) {
|
|
11432
|
-
throw new
|
|
11496
|
+
throw new HTTPException20(403, { message: error.message });
|
|
11433
11497
|
}
|
|
11434
11498
|
if (error instanceof SessionIdConflictError) {
|
|
11435
|
-
throw new
|
|
11499
|
+
throw new HTTPException20(409, {
|
|
11436
11500
|
message: "requested session id is already in use"
|
|
11437
11501
|
});
|
|
11438
11502
|
}
|
|
11439
11503
|
if (error instanceof NewSessionDraftConflictError) {
|
|
11440
|
-
throw new
|
|
11504
|
+
throw new HTTPException20(409, {
|
|
11441
11505
|
message: error.message,
|
|
11442
11506
|
cause: error
|
|
11443
11507
|
});
|
|
11444
11508
|
}
|
|
11445
11509
|
if (error instanceof SessionCreateIdempotencyConflictError) {
|
|
11446
|
-
throw new
|
|
11510
|
+
throw new HTTPException20(409, { message: error.message, cause: error });
|
|
11447
11511
|
}
|
|
11448
11512
|
throw error;
|
|
11449
11513
|
}
|
|
@@ -11472,7 +11536,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload, aut
|
|
|
11472
11536
|
function prepareSelectedHostTurnAuthority(settings, tools, grant, workspaceId, selections, authorization) {
|
|
11473
11537
|
if (!selections.length) return void 0;
|
|
11474
11538
|
if (!authorization || authorization.grant.accountId !== grant.accountId || authorization.grant.subjectId !== grant.subjectId || authorization?.grant.workspaceId !== workspaceId || !hasPermission(authorization?.grant.permissions ?? [], "connections:read") || !hasPermission(grant.permissions, "connections:read") || grant.metadata?.["sessionId"])
|
|
11475
|
-
throw new
|
|
11539
|
+
throw new HTTPException20(403, {
|
|
11476
11540
|
message: "Host selection requires a verified direct owner"
|
|
11477
11541
|
});
|
|
11478
11542
|
const beforeCommit = prepareHostMcpOwnerAuthorization(
|
|
@@ -11484,7 +11548,7 @@ function prepareSelectedHostTurnAuthority(settings, tools, grant, workspaceId, s
|
|
|
11484
11548
|
selections.map((selection) => {
|
|
11485
11549
|
const configured = settings.mcpServers.find((server) => server.id === selection.serverId);
|
|
11486
11550
|
if (!configured || !tools.some((tool) => tool.kind === "mcp" && tool.id === selection.serverId) || configured.connectionRef?.authoritySource !== "host" || !configured.connectionRef.hostBinding || !configured.url)
|
|
11487
|
-
throw new
|
|
11551
|
+
throw new HTTPException20(422, {
|
|
11488
11552
|
message: "Host delegation must match a selected configured host server"
|
|
11489
11553
|
});
|
|
11490
11554
|
assertHostMcpAuthoritySourceAdmissionEnabled(settings, configured.connectionRef);
|
|
@@ -11504,7 +11568,7 @@ function prepareSelectedHostTurnAuthority(settings, tools, grant, workspaceId, s
|
|
|
11504
11568
|
connectionRef
|
|
11505
11569
|
});
|
|
11506
11570
|
if (!delegation || !binding || delegation.status !== "active" || binding.status !== "active" || !definition.success || delegation.generation !== selection.generation || binding.id !== hostBinding.bindingId || binding.generation !== hostBinding.generation || stableJson4(definition.data) !== stableJson4(binding.definition))
|
|
11507
|
-
throw new
|
|
11571
|
+
throw new HTTPException20(403, { message: "Host delegation selection changed" });
|
|
11508
11572
|
try {
|
|
11509
11573
|
await captureDirectHostMcpAuthority(tx, owner, {
|
|
11510
11574
|
sessionId,
|
|
@@ -11514,9 +11578,9 @@ function prepareSelectedHostTurnAuthority(settings, tools, grant, workspaceId, s
|
|
|
11514
11578
|
});
|
|
11515
11579
|
} catch (error) {
|
|
11516
11580
|
if (error instanceof HostMcpDelegationAuthorityError2)
|
|
11517
|
-
throw new
|
|
11581
|
+
throw new HTTPException20(403, { message: "Host delegation authority unavailable" });
|
|
11518
11582
|
if (error instanceof HostMcpBindingConflictError)
|
|
11519
|
-
throw new
|
|
11583
|
+
throw new HTTPException20(409, { message: "Host delegation selection conflicts" });
|
|
11520
11584
|
throw error;
|
|
11521
11585
|
}
|
|
11522
11586
|
}
|
|
@@ -11556,7 +11620,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
|
|
|
11556
11620
|
(a, b) => a.serverId < b.serverId ? -1 : a.serverId > b.serverId ? 1 : 0
|
|
11557
11621
|
);
|
|
11558
11622
|
if (hostSelections.length && (!input.authorization || input.authorization.grant.accountId !== grant.accountId || input.authorization.grant.subjectId !== grant.subjectId || input.authorization?.grant.workspaceId !== workspaceId || !hasPermission(input.authorization?.grant.permissions ?? [], "connections:read") || !hasPermission(grant.permissions, "connections:read") || grant.metadata?.["sessionId"]))
|
|
11559
|
-
throw new
|
|
11623
|
+
throw new HTTPException20(403, {
|
|
11560
11624
|
message: "Host selection requires a verified direct owner"
|
|
11561
11625
|
});
|
|
11562
11626
|
if (hostSelections.length)
|
|
@@ -11661,7 +11725,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
|
|
|
11661
11725
|
assertSessionAllowsProductModel(existingSession, effectiveModel);
|
|
11662
11726
|
} catch (error) {
|
|
11663
11727
|
if (error instanceof CodexCompactionV2ProviderLockedError) {
|
|
11664
|
-
throw new
|
|
11728
|
+
throw new HTTPException20(422, { message: error.message, cause: error });
|
|
11665
11729
|
}
|
|
11666
11730
|
throw error;
|
|
11667
11731
|
}
|
|
@@ -11684,7 +11748,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
|
|
|
11684
11748
|
(resource) => !acceptedResources.has(stableJson4(resource))
|
|
11685
11749
|
);
|
|
11686
11750
|
if (unacceptedDraftResource) {
|
|
11687
|
-
throw new
|
|
11751
|
+
throw new HTTPException20(422, {
|
|
11688
11752
|
message: "composer draft resources must be included in the accepted resource set"
|
|
11689
11753
|
});
|
|
11690
11754
|
}
|
|
@@ -11703,7 +11767,7 @@ async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, ses
|
|
|
11703
11767
|
model: effectiveModel
|
|
11704
11768
|
});
|
|
11705
11769
|
if (requestedResources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
11706
|
-
throw new
|
|
11770
|
+
throw new HTTPException20(503, {
|
|
11707
11771
|
message: "object storage is not configured"
|
|
11708
11772
|
});
|
|
11709
11773
|
}
|
|
@@ -11889,7 +11953,7 @@ async function updateSessionMcpApprovalPolicy(deps, grant, sessionId, serverId,
|
|
|
11889
11953
|
async (_session, context) => {
|
|
11890
11954
|
const result = await context.updateSessionMcpApprovalPolicy(serverId, normalizedPolicy);
|
|
11891
11955
|
if (!result.server) {
|
|
11892
|
-
throw new
|
|
11956
|
+
throw new HTTPException20(404, {
|
|
11893
11957
|
message: "session MCP server not found"
|
|
11894
11958
|
});
|
|
11895
11959
|
}
|
|
@@ -11973,7 +12037,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11973
12037
|
(tool) => !validatedIds.has(`${tool.kind}:${tool.id}`)
|
|
11974
12038
|
);
|
|
11975
12039
|
if (unknown) {
|
|
11976
|
-
throw new
|
|
12040
|
+
throw new HTTPException20(422, {
|
|
11977
12041
|
message: `unknown MCP server id: ${unknown.id}`
|
|
11978
12042
|
});
|
|
11979
12043
|
}
|
|
@@ -11985,7 +12049,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
11985
12049
|
(tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool)
|
|
11986
12050
|
);
|
|
11987
12051
|
if (disallowedFirstPartyMcpTool) {
|
|
11988
|
-
throw new
|
|
12052
|
+
throw new HTTPException20(422, {
|
|
11989
12053
|
message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`
|
|
11990
12054
|
});
|
|
11991
12055
|
}
|
|
@@ -12018,7 +12082,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
12018
12082
|
if (session.parentSessionId) {
|
|
12019
12083
|
const parent = await context.getLockedSession(session.parentSessionId);
|
|
12020
12084
|
if (!parent) {
|
|
12021
|
-
throw new
|
|
12085
|
+
throw new HTTPException20(409, {
|
|
12022
12086
|
message: "parent session is no longer available"
|
|
12023
12087
|
});
|
|
12024
12088
|
}
|
|
@@ -12040,7 +12104,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
12040
12104
|
].filter((tool) => deploymentAllowedFirstPartyMcpTools.has(tool));
|
|
12041
12105
|
if (requestedMode === "workspace_default") {
|
|
12042
12106
|
if (!parentTracksWorkspaceDefaults) {
|
|
12043
|
-
throw new
|
|
12107
|
+
throw new HTTPException20(403, {
|
|
12044
12108
|
message: "a child may adopt workspace defaults only while its parent tracks workspace defaults"
|
|
12045
12109
|
});
|
|
12046
12110
|
}
|
|
@@ -12062,7 +12126,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
12062
12126
|
(tool) => !parentFirstPartySet.has(tool)
|
|
12063
12127
|
);
|
|
12064
12128
|
if (widenedFirstPartyTool) {
|
|
12065
|
-
throw new
|
|
12129
|
+
throw new HTTPException20(403, {
|
|
12066
12130
|
message: `session OpenGeni tools may only narrow the parent policy: ${widenedFirstPartyTool}`
|
|
12067
12131
|
});
|
|
12068
12132
|
}
|
|
@@ -12100,7 +12164,7 @@ async function updateSessionToolPolicy(deps, grant, sessionId, request) {
|
|
|
12100
12164
|
(tool) => !currentFirstPartyCeiling.has(tool)
|
|
12101
12165
|
);
|
|
12102
12166
|
if (widenedFirstPartyTool) {
|
|
12103
|
-
throw new
|
|
12167
|
+
throw new HTTPException20(403, {
|
|
12104
12168
|
message: `an agent may only narrow its session OpenGeni tools: ${widenedFirstPartyTool}`
|
|
12105
12169
|
});
|
|
12106
12170
|
}
|
|
@@ -12167,13 +12231,13 @@ async function readSessionLineage(deps, grant, sessionId) {
|
|
|
12167
12231
|
if (authorization?.relatedSessionAccess === "target") {
|
|
12168
12232
|
const session = await getSession2(deps.db, grant.workspaceId, sessionId);
|
|
12169
12233
|
if (!session) {
|
|
12170
|
-
throw new
|
|
12234
|
+
throw new HTTPException20(404, { message: "session not found" });
|
|
12171
12235
|
}
|
|
12172
12236
|
return { ancestors: [], children: [], truncated: false };
|
|
12173
12237
|
}
|
|
12174
12238
|
const lineage = await getSessionLineage(deps.db, grant.workspaceId, sessionId);
|
|
12175
12239
|
if (!lineage) {
|
|
12176
|
-
throw new
|
|
12240
|
+
throw new HTTPException20(404, { message: "session not found" });
|
|
12177
12241
|
}
|
|
12178
12242
|
return lineage;
|
|
12179
12243
|
}
|
|
@@ -12209,7 +12273,7 @@ function workspaceCustomModelCommitGuard(input) {
|
|
|
12209
12273
|
reference
|
|
12210
12274
|
});
|
|
12211
12275
|
if (!active) {
|
|
12212
|
-
throw new
|
|
12276
|
+
throw new HTTPException21(422, {
|
|
12213
12277
|
message: `model is not available: ${input.modelId}`
|
|
12214
12278
|
});
|
|
12215
12279
|
}
|
|
@@ -12369,7 +12433,7 @@ async function createValidatedScheduledTask(input) {
|
|
|
12369
12433
|
async function frozenScheduledTaskCreatorPolicy(input) {
|
|
12370
12434
|
const session = await getSession3(input.db, input.grant.workspaceId, input.sessionId);
|
|
12371
12435
|
if (!session) {
|
|
12372
|
-
throw new
|
|
12436
|
+
throw new HTTPException21(403, {
|
|
12373
12437
|
message: "the calling agent session is not available in this workspace"
|
|
12374
12438
|
});
|
|
12375
12439
|
}
|
|
@@ -12379,7 +12443,7 @@ async function frozenScheduledTaskCreatorPolicy(input) {
|
|
|
12379
12443
|
);
|
|
12380
12444
|
const firstPartyMcpPermissions = (session.firstPartyMcpPermissions ?? [...DEFAULT_FIRST_PARTY_MCP_PERMISSIONS2]).filter((permission) => hasPermission(input.grant.permissions, permission));
|
|
12381
12445
|
if (firstPartyMcpPermissions.length === 0) {
|
|
12382
|
-
throw new
|
|
12446
|
+
throw new HTTPException21(403, {
|
|
12383
12447
|
message: "the calling agent session holds no first-party MCP permission it could delegate to scheduled runs"
|
|
12384
12448
|
});
|
|
12385
12449
|
}
|
|
@@ -12422,7 +12486,7 @@ async function withScheduledTaskAuthorityWriteErrors(run) {
|
|
|
12422
12486
|
} catch (error) {
|
|
12423
12487
|
if (nestedPostgresSqlState5(error) === "42501") {
|
|
12424
12488
|
const detail = nestedPostgresMessage(error);
|
|
12425
|
-
throw new
|
|
12489
|
+
throw new HTTPException21(409, {
|
|
12426
12490
|
message: detail ? `scheduled task authority denied: ${detail}` : "scheduled task authority denied"
|
|
12427
12491
|
});
|
|
12428
12492
|
}
|
|
@@ -12437,20 +12501,20 @@ async function updateScheduledTaskForApi(db, workspaceId, taskId, update) {
|
|
|
12437
12501
|
async function validateScheduledTaskTarget(input) {
|
|
12438
12502
|
if (input.runMode !== "existing_session") {
|
|
12439
12503
|
if (input.targetSessionId) {
|
|
12440
|
-
throw new
|
|
12504
|
+
throw new HTTPException21(422, {
|
|
12441
12505
|
message: "targetSessionId requires runMode=existing_session"
|
|
12442
12506
|
});
|
|
12443
12507
|
}
|
|
12444
12508
|
return null;
|
|
12445
12509
|
}
|
|
12446
12510
|
if (!input.targetSessionId) {
|
|
12447
|
-
throw new
|
|
12511
|
+
throw new HTTPException21(input.missingTargetStatus ?? 422, {
|
|
12448
12512
|
message: input.missingTargetStatus === 404 ? "target session not found" : "targetSessionId is required when runMode=existing_session"
|
|
12449
12513
|
});
|
|
12450
12514
|
}
|
|
12451
12515
|
requirePermission(input.grant, "sessions:control");
|
|
12452
12516
|
if (input.agentConfig.goal) {
|
|
12453
|
-
throw new
|
|
12517
|
+
throw new HTTPException21(422, {
|
|
12454
12518
|
message: "agentConfig.goal cannot be used with an existing-session target"
|
|
12455
12519
|
});
|
|
12456
12520
|
}
|
|
@@ -12469,44 +12533,44 @@ async function validateScheduledTaskTarget(input) {
|
|
|
12469
12533
|
);
|
|
12470
12534
|
} catch (error) {
|
|
12471
12535
|
if (error instanceof SessionAuthorizationDeniedError) {
|
|
12472
|
-
throw new
|
|
12536
|
+
throw new HTTPException21(404, { message: "target session not found" });
|
|
12473
12537
|
}
|
|
12474
12538
|
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
12475
|
-
throw new
|
|
12539
|
+
throw new HTTPException21(503, { message: "session authorization is unavailable" });
|
|
12476
12540
|
}
|
|
12477
12541
|
throw error;
|
|
12478
12542
|
}
|
|
12479
12543
|
const session = await getSession3(input.db, input.grant.workspaceId, input.targetSessionId);
|
|
12480
12544
|
if (!session || session.accountId !== input.grant.accountId) {
|
|
12481
|
-
throw new
|
|
12545
|
+
throw new HTTPException21(404, { message: "target session not found" });
|
|
12482
12546
|
}
|
|
12483
12547
|
if (input.agentConfig.bundledSkillIds !== void 0 && !isDeepStrictEqual(input.agentConfig.bundledSkillIds, session.bundledSkillIds)) {
|
|
12484
|
-
throw new
|
|
12548
|
+
throw new HTTPException21(422, {
|
|
12485
12549
|
message: "An existing-session schedule cannot change that session's bundled Skill selection"
|
|
12486
12550
|
});
|
|
12487
12551
|
}
|
|
12488
12552
|
if (session.status === "cancelled") {
|
|
12489
|
-
throw new
|
|
12553
|
+
throw new HTTPException21(409, {
|
|
12490
12554
|
message: "target session is cancelled; choose a revivable session"
|
|
12491
12555
|
});
|
|
12492
12556
|
}
|
|
12493
12557
|
if ((session.variableSetId ?? null) !== (input.variableSetId ?? null)) {
|
|
12494
|
-
throw new
|
|
12558
|
+
throw new HTTPException21(422, {
|
|
12495
12559
|
message: "target session variableSet attachment does not match the scheduled task"
|
|
12496
12560
|
});
|
|
12497
12561
|
}
|
|
12498
12562
|
if ((session.rigId ?? null) !== (input.rigId ?? null)) {
|
|
12499
|
-
throw new
|
|
12563
|
+
throw new HTTPException21(422, {
|
|
12500
12564
|
message: "target session rig does not match the scheduled task"
|
|
12501
12565
|
});
|
|
12502
12566
|
}
|
|
12503
12567
|
if (input.agentConfig.sandboxBackend !== void 0 && input.agentConfig.sandboxBackend !== session.sandboxBackend) {
|
|
12504
|
-
throw new
|
|
12568
|
+
throw new HTTPException21(422, {
|
|
12505
12569
|
message: "target session sandbox backend does not match the scheduled task"
|
|
12506
12570
|
});
|
|
12507
12571
|
}
|
|
12508
12572
|
if (scheduledSlackBotConnectionId(session.metadata) !== (input.agentConfig.slackBotConnectionId ?? null)) {
|
|
12509
|
-
throw new
|
|
12573
|
+
throw new HTTPException21(422, {
|
|
12510
12574
|
message: "target session OpenGeni Slack bot binding does not match the scheduled task"
|
|
12511
12575
|
});
|
|
12512
12576
|
}
|
|
@@ -12516,19 +12580,19 @@ async function validateScheduledTaskMachineTarget(input) {
|
|
|
12516
12580
|
const machineTarget = input.agentConfig.machineTarget;
|
|
12517
12581
|
if (!machineTarget) {
|
|
12518
12582
|
if (input.runMode !== "existing_session" && (input.agentConfig.sandboxBackend ?? input.settings.sandboxBackend) === "selfhosted") {
|
|
12519
|
-
throw new
|
|
12583
|
+
throw new HTTPException21(422, {
|
|
12520
12584
|
message: "self-hosted scheduled tasks require a Connected Machine; select a machine before saving"
|
|
12521
12585
|
});
|
|
12522
12586
|
}
|
|
12523
12587
|
return null;
|
|
12524
12588
|
}
|
|
12525
12589
|
if (input.runMode === "existing_session") {
|
|
12526
|
-
throw new
|
|
12590
|
+
throw new HTTPException21(422, {
|
|
12527
12591
|
message: "machineTarget cannot be used with an existing-session target"
|
|
12528
12592
|
});
|
|
12529
12593
|
}
|
|
12530
12594
|
if (!input.settings.sandboxOwnershipEnabled || !input.settings.sandboxSelfhostedEnabled) {
|
|
12531
|
-
throw new
|
|
12595
|
+
throw new HTTPException21(422, {
|
|
12532
12596
|
message: "Connected Machines are not enabled for scheduled tasks in this deployment"
|
|
12533
12597
|
});
|
|
12534
12598
|
}
|
|
@@ -12539,23 +12603,23 @@ async function validateScheduledTaskMachineTarget(input) {
|
|
|
12539
12603
|
};
|
|
12540
12604
|
const sandbox = await getSandbox4(input.db, access, machineTarget.targetSandboxId);
|
|
12541
12605
|
if (!sandbox || sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
|
|
12542
|
-
throw new
|
|
12606
|
+
throw new HTTPException21(422, {
|
|
12543
12607
|
message: "the selected Connected Machine is unavailable"
|
|
12544
12608
|
});
|
|
12545
12609
|
}
|
|
12546
12610
|
if (sandbox.scope === "user") {
|
|
12547
|
-
throw new
|
|
12611
|
+
throw new HTTPException21(422, {
|
|
12548
12612
|
message: "personal Connected Machines cannot run unattended schedules; select a workspace or organization machine"
|
|
12549
12613
|
});
|
|
12550
12614
|
}
|
|
12551
12615
|
const enrollment = input.requireOnline ? await getLiveEnrollmentConnection3(input.db, access, sandbox.enrollmentId) : await getEnrollment3(input.db, access, sandbox.enrollmentId);
|
|
12552
12616
|
if (!enrollment || enrollment.status !== "active") {
|
|
12553
|
-
throw new
|
|
12617
|
+
throw new HTTPException21(422, {
|
|
12554
12618
|
message: input.requireOnline ? "the selected Connected Machine is offline" : "the selected Connected Machine is unavailable"
|
|
12555
12619
|
});
|
|
12556
12620
|
}
|
|
12557
12621
|
if (input.requireOnline && !enrollment.workspaceRoot) {
|
|
12558
|
-
throw new
|
|
12622
|
+
throw new HTTPException21(422, {
|
|
12559
12623
|
message: "the selected Connected Machine has not reported a workspace root; reconnect it with a current agent"
|
|
12560
12624
|
});
|
|
12561
12625
|
}
|
|
@@ -12589,25 +12653,25 @@ function scheduledTaskAuthorityUpdateForGrant(grant) {
|
|
|
12589
12653
|
async function requireScheduledTaskRig(db, access, rigId) {
|
|
12590
12654
|
const rig = await getRig5(db, access, rigId);
|
|
12591
12655
|
if (!rig) {
|
|
12592
|
-
throw new
|
|
12656
|
+
throw new HTTPException21(422, { message: `unknown rigId: ${rigId}` });
|
|
12593
12657
|
}
|
|
12594
12658
|
}
|
|
12595
12659
|
async function validatedScheduledTaskUpdate(input) {
|
|
12596
12660
|
const update = {};
|
|
12597
12661
|
const existingKnowledge = input.existing.action.kind === "knowledge_source_sync";
|
|
12598
12662
|
if (input.payload.action && input.payload.action.kind !== input.existing.action.kind) {
|
|
12599
|
-
throw new
|
|
12663
|
+
throw new HTTPException21(409, {
|
|
12600
12664
|
message: "scheduled task action kind is immutable; create a new schedule"
|
|
12601
12665
|
});
|
|
12602
12666
|
}
|
|
12603
12667
|
if (existingKnowledge) {
|
|
12604
12668
|
if (input.payload.agentConfig !== void 0 || input.payload.runMode !== void 0 || input.payload.targetSessionId !== void 0 || input.payload.variableSetId !== void 0 || input.payload.rigId !== void 0 || input.payload.connectionAuthorities !== void 0 || input.payload.selectedHostMcpDelegations !== void 0) {
|
|
12605
|
-
throw new
|
|
12669
|
+
throw new HTTPException21(422, {
|
|
12606
12670
|
message: "knowledge source schedules do not accept agent/session configuration"
|
|
12607
12671
|
});
|
|
12608
12672
|
}
|
|
12609
12673
|
if (input.payload.overlapPolicy === "allow_concurrent") {
|
|
12610
|
-
throw new
|
|
12674
|
+
throw new HTTPException21(422, {
|
|
12611
12675
|
message: "knowledge source schedules require skip or buffer_one overlap"
|
|
12612
12676
|
});
|
|
12613
12677
|
}
|
|
@@ -12636,7 +12700,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12636
12700
|
const nextRunMode = input.payload.runMode ?? input.existing.runMode;
|
|
12637
12701
|
const nextTargetSessionId = input.payload.targetSessionId !== void 0 ? input.payload.targetSessionId : nextRunMode === "existing_session" ? existingTarget : null;
|
|
12638
12702
|
if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId && nextRunMode === "existing_session") {
|
|
12639
|
-
throw new
|
|
12703
|
+
throw new HTTPException21(409, {
|
|
12640
12704
|
message: "cannot target an existing session after this task created its reusable session; create a new task"
|
|
12641
12705
|
});
|
|
12642
12706
|
}
|
|
@@ -12662,7 +12726,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12662
12726
|
if (input.payload.variableSetId !== void 0) {
|
|
12663
12727
|
const nextVariableSetId = input.payload.variableSetId;
|
|
12664
12728
|
if ((input.existing.variableSetId ?? null) !== (nextVariableSetId ?? null) && input.existing.runMode === "reusable_session" && input.existing.reusableSessionId) {
|
|
12665
|
-
throw new
|
|
12729
|
+
throw new HTTPException21(409, {
|
|
12666
12730
|
message: "cannot change variableSet of a task with a live reusable session; recreate the task"
|
|
12667
12731
|
});
|
|
12668
12732
|
}
|
|
@@ -12683,7 +12747,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12683
12747
|
}
|
|
12684
12748
|
if (input.payload.rigId !== void 0) {
|
|
12685
12749
|
if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId !== null && input.payload.rigId !== input.existing.rigId) {
|
|
12686
|
-
throw new
|
|
12750
|
+
throw new HTTPException21(409, {
|
|
12687
12751
|
message: "A reusable-session task cannot change rigId after materialization; recreate it"
|
|
12688
12752
|
});
|
|
12689
12753
|
}
|
|
@@ -12715,7 +12779,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12715
12779
|
...input.toolsProvided !== void 0 ? { toolsProvided: input.toolsProvided } : {}
|
|
12716
12780
|
});
|
|
12717
12781
|
if (input.existing.reusableSessionId && input.existing.runMode === "reusable_session" && (input.existing.agentConfig.slackBotConnectionId ?? null) !== (nextAgentConfig2.slackBotConnectionId ?? null)) {
|
|
12718
|
-
throw new
|
|
12782
|
+
throw new HTTPException21(409, {
|
|
12719
12783
|
message: "cannot change the OpenGeni Slack bot connection of a task with a live reusable session; recreate the task"
|
|
12720
12784
|
});
|
|
12721
12785
|
}
|
|
@@ -12739,7 +12803,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12739
12803
|
input.existing.id
|
|
12740
12804
|
);
|
|
12741
12805
|
if (existingXaiAuthority.scope === "user" && materialExecutionChange && (input.existing.createdBy.kind !== "subject" || input.existing.createdBy.subjectId !== input.grant.subjectId)) {
|
|
12742
|
-
throw new
|
|
12806
|
+
throw new HTTPException21(409, {
|
|
12743
12807
|
message: "changing a user-scoped xAI scheduled task requires the same causal human"
|
|
12744
12808
|
});
|
|
12745
12809
|
}
|
|
@@ -12750,12 +12814,12 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12750
12814
|
);
|
|
12751
12815
|
if (input.payload.connectionAuthorities === void 0) {
|
|
12752
12816
|
if (materialExecutionChange && existingDelegations.some((delegation) => delegation.connectionType === "github_personal")) {
|
|
12753
|
-
throw new
|
|
12817
|
+
throw new HTTPException21(409, {
|
|
12754
12818
|
message: "material changes to a personal GitHub-authorized task require explicit connectionAuthorities"
|
|
12755
12819
|
});
|
|
12756
12820
|
}
|
|
12757
12821
|
if (existingDelegations.length > 0 && !isDeepStrictEqual(nextAgentConfig.tools, input.existing.agentConfig.tools)) {
|
|
12758
|
-
throw new
|
|
12822
|
+
throw new HTTPException21(409, {
|
|
12759
12823
|
message: "changing tools on a connection-authorized task requires explicit connectionAuthorities"
|
|
12760
12824
|
});
|
|
12761
12825
|
}
|
|
@@ -12763,7 +12827,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12763
12827
|
if (materialExecutionChange && existingDelegations.some(
|
|
12764
12828
|
(delegation) => delegation.ownerSubjectId !== input.grant.subjectId
|
|
12765
12829
|
)) {
|
|
12766
|
-
throw new
|
|
12830
|
+
throw new HTTPException21(409, {
|
|
12767
12831
|
message: "preserving connection authority requires the same causal human; provide a new explicit selection"
|
|
12768
12832
|
});
|
|
12769
12833
|
}
|
|
@@ -12775,7 +12839,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12775
12839
|
}
|
|
12776
12840
|
} else if (input.payload.connectionAuthorities.length === 0) {
|
|
12777
12841
|
if (personalGitHubRepositoryResources(nextAgentConfig.resources).length > 0) {
|
|
12778
|
-
throw new
|
|
12842
|
+
throw new HTTPException21(409, {
|
|
12779
12843
|
message: "personal GitHub repository resources cannot be retained without connectionAuthorities"
|
|
12780
12844
|
});
|
|
12781
12845
|
}
|
|
@@ -12885,7 +12949,7 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
12885
12949
|
async function requireScheduledTaskForApi(db, workspaceId, taskId) {
|
|
12886
12950
|
const task = await getScheduledTask(db, workspaceId, taskId);
|
|
12887
12951
|
if (!task) {
|
|
12888
|
-
throw new
|
|
12952
|
+
throw new HTTPException21(404, { message: "scheduled task not found" });
|
|
12889
12953
|
}
|
|
12890
12954
|
return task;
|
|
12891
12955
|
}
|
|
@@ -12919,12 +12983,12 @@ async function restoreScheduledTask(db, previous) {
|
|
|
12919
12983
|
}
|
|
12920
12984
|
async function validateKnowledgeSourceSyncAction(input) {
|
|
12921
12985
|
if (input.action.initiatingSubjectId !== input.grant.subjectId) {
|
|
12922
|
-
throw new
|
|
12986
|
+
throw new HTTPException21(403, {
|
|
12923
12987
|
message: "knowledge source sync must preserve the exact initiating subject"
|
|
12924
12988
|
});
|
|
12925
12989
|
}
|
|
12926
12990
|
if (input.action.connection.ownerSubjectId !== input.grant.subjectId) {
|
|
12927
|
-
throw new
|
|
12991
|
+
throw new HTTPException21(403, {
|
|
12928
12992
|
message: "knowledge source connection must belong to the initiating subject"
|
|
12929
12993
|
});
|
|
12930
12994
|
}
|
|
@@ -12935,10 +12999,10 @@ async function validateKnowledgeSourceSyncAction(input) {
|
|
|
12935
12999
|
initiatingSubjectId: input.grant.subjectId
|
|
12936
13000
|
});
|
|
12937
13001
|
if (!resolved || resolved.source.lifecycleState !== "active") {
|
|
12938
|
-
throw new
|
|
13002
|
+
throw new HTTPException21(404, { message: "knowledge source not found" });
|
|
12939
13003
|
}
|
|
12940
13004
|
if (resolved.source.syncGeneration !== input.action.sourceGeneration || resolved.source.lifecycleGeneration !== input.action.sourceLifecycleGeneration || scopedKnowledgeScopeKey(resolved.source.scope) !== scopedKnowledgeScopeKey(input.action.destination)) {
|
|
12941
|
-
throw new
|
|
13005
|
+
throw new HTTPException21(409, {
|
|
12942
13006
|
message: "knowledge source authority or generation changed"
|
|
12943
13007
|
});
|
|
12944
13008
|
}
|
|
@@ -12949,7 +13013,7 @@ async function validateKnowledgeSourceSyncAction(input) {
|
|
|
12949
13013
|
input.grant.subjectId
|
|
12950
13014
|
);
|
|
12951
13015
|
if (!connection || connection.accountId !== input.grant.accountId || connection.workspaceId !== input.grant.workspaceId || connection.subjectId !== input.action.connection.ownerSubjectId || connection.version !== input.action.connection.connectionVersion || connection.providerDomain.toLowerCase() !== input.action.connection.providerDomain.toLowerCase() || connection.kind !== input.action.connection.kind || connection.status !== "active") {
|
|
12952
|
-
throw new
|
|
13016
|
+
throw new HTTPException21(409, {
|
|
12953
13017
|
message: "knowledge source connection authority changed or requires reconnect"
|
|
12954
13018
|
});
|
|
12955
13019
|
}
|
|
@@ -12973,10 +13037,10 @@ async function deleteScheduledTaskLifecycle(input) {
|
|
|
12973
13037
|
input.workspaceId,
|
|
12974
13038
|
input.taskId
|
|
12975
13039
|
);
|
|
12976
|
-
if (!task2) throw new
|
|
13040
|
+
if (!task2) throw new HTTPException21(404, { message: "Scheduled task not found" });
|
|
12977
13041
|
const wasLive = task2.deletedAt === null;
|
|
12978
13042
|
if (task2.action.kind === "knowledge_source_sync" && (task2.action.initiatingSubjectId !== input.subjectId || task2.action.connection.ownerSubjectId !== input.subjectId)) {
|
|
12979
|
-
throw new
|
|
13043
|
+
throw new HTTPException21(403, {
|
|
12980
13044
|
message: "knowledge source schedule requires the exact initiating subject"
|
|
12981
13045
|
});
|
|
12982
13046
|
}
|
|
@@ -13049,7 +13113,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
13049
13113
|
const actor = creationInitiatorForGrant(input.grant).actor;
|
|
13050
13114
|
const parent = actor ? await getSession3(input.db, input.workspaceId, actor.sessionId) : null;
|
|
13051
13115
|
if (actor && (!parent || parent.accountId !== input.grant.accountId)) {
|
|
13052
|
-
throw new
|
|
13116
|
+
throw new HTTPException21(403, {
|
|
13053
13117
|
message: "Scheduled Skill selection requires the creating agent's session"
|
|
13054
13118
|
});
|
|
13055
13119
|
}
|
|
@@ -13060,7 +13124,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
13060
13124
|
parent?.bundledSkillIds
|
|
13061
13125
|
);
|
|
13062
13126
|
} catch (error) {
|
|
13063
|
-
throw new
|
|
13127
|
+
throw new HTTPException21(422, {
|
|
13064
13128
|
message: error instanceof Error ? error.message : "Invalid bundled Skill selection"
|
|
13065
13129
|
});
|
|
13066
13130
|
}
|
|
@@ -13084,16 +13148,16 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
13084
13148
|
);
|
|
13085
13149
|
const prompt = input.payload.agentConfig.prompt.trim();
|
|
13086
13150
|
if (!prompt) {
|
|
13087
|
-
throw new
|
|
13151
|
+
throw new HTTPException21(422, { message: "scheduled task prompt is required" });
|
|
13088
13152
|
}
|
|
13089
13153
|
if (hasReservedOpenGeniSlackBotSessionMetadata(input.payload.agentConfig.metadata)) {
|
|
13090
|
-
throw new
|
|
13154
|
+
throw new HTTPException21(422, {
|
|
13091
13155
|
message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY3} is reserved for scheduler routing`
|
|
13092
13156
|
});
|
|
13093
13157
|
}
|
|
13094
13158
|
await validateGitHubRepositorySelection(input.db, input.workspaceId, resources);
|
|
13095
13159
|
if (resources.some((resource) => resource.kind === "file") && !input.objectStorage) {
|
|
13096
|
-
throw new
|
|
13160
|
+
throw new HTTPException21(503, { message: "object storage is not configured" });
|
|
13097
13161
|
}
|
|
13098
13162
|
await validateFileResources(
|
|
13099
13163
|
input.db,
|
|
@@ -13116,7 +13180,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
13116
13180
|
const deploymentPolicy = await getNestedAgentDepthDeploymentPolicy(input.db);
|
|
13117
13181
|
const inheritedMaxDepth = typeof workspaceMaxDepth === "number" ? workspaceMaxDepth : deploymentPolicy.maxNestedAgentDepth;
|
|
13118
13182
|
if (requestedMaxDepth > inheritedMaxDepth && !hasPermission(input.grant.permissions, "workspace:admin")) {
|
|
13119
|
-
throw new
|
|
13183
|
+
throw new HTTPException21(403, {
|
|
13120
13184
|
message: `scheduled task maxNestedAgentDepth ${requestedMaxDepth} exceeds inherited limit ${inheritedMaxDepth}; workspace:admin is required to increase it`
|
|
13121
13185
|
});
|
|
13122
13186
|
}
|
|
@@ -13137,13 +13201,13 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
13137
13201
|
const preflight = agentConfig.incidentTelemetryPreflight;
|
|
13138
13202
|
if (executionClass === void 0 && preflight === void 0) return;
|
|
13139
13203
|
if (executionClass !== "incident_telemetry" || !preflight) {
|
|
13140
|
-
throw new
|
|
13204
|
+
throw new HTTPException21(422, {
|
|
13141
13205
|
message: "executionClass=incident_telemetry and incidentTelemetryPreflight must be configured together"
|
|
13142
13206
|
});
|
|
13143
13207
|
}
|
|
13144
13208
|
for (const required of preflight.requiredResources) {
|
|
13145
13209
|
if (!agentConfig.resources.some((selected) => isDeepStrictEqual(selected, required))) {
|
|
13146
|
-
throw new
|
|
13210
|
+
throw new HTTPException21(422, {
|
|
13147
13211
|
message: "incidentTelemetryPreflight.requiredResources must be exact selected resources"
|
|
13148
13212
|
});
|
|
13149
13213
|
}
|
|
@@ -13151,13 +13215,13 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
13151
13215
|
const selectedMcpServerIds = new Set(agentConfig.tools.map((tool) => tool.id));
|
|
13152
13216
|
selectedMcpServerIds.add("opengeni");
|
|
13153
13217
|
if (preflight.requiredMcpServerIds.some((id) => !selectedMcpServerIds.has(id))) {
|
|
13154
|
-
throw new
|
|
13218
|
+
throw new HTTPException21(422, {
|
|
13155
13219
|
message: "incidentTelemetryPreflight.requiredMcpServerIds must be exact selected MCP servers"
|
|
13156
13220
|
});
|
|
13157
13221
|
}
|
|
13158
13222
|
const selectedFirstPartyTools = new Set(resolveFirstPartyMcpToolPolicy2(settings).default);
|
|
13159
13223
|
if (preflight.requiredFirstPartyMcpTools.some((tool) => !selectedFirstPartyTools.has(tool))) {
|
|
13160
|
-
throw new
|
|
13224
|
+
throw new HTTPException21(422, {
|
|
13161
13225
|
message: "incidentTelemetryPreflight.requiredFirstPartyMcpTools must be present in the selected first-party tool policy"
|
|
13162
13226
|
});
|
|
13163
13227
|
}
|
|
@@ -13165,18 +13229,18 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
13165
13229
|
if ((preflight.requiredFirstPartyMcpPermissions ?? []).some(
|
|
13166
13230
|
(permission) => !selectedFirstPartyPermissions.has(permission)
|
|
13167
13231
|
)) {
|
|
13168
|
-
throw new
|
|
13232
|
+
throw new HTTPException21(422, {
|
|
13169
13233
|
message: "incidentTelemetryPreflight.requiredFirstPartyMcpPermissions must be present in the scheduled responder permission policy"
|
|
13170
13234
|
});
|
|
13171
13235
|
}
|
|
13172
13236
|
const route = preflight.dataSource.route;
|
|
13173
13237
|
if (route.kind === "mcp" && !selectedMcpServerIds.has(route.serverId)) {
|
|
13174
|
-
throw new
|
|
13238
|
+
throw new HTTPException21(422, {
|
|
13175
13239
|
message: "incidentTelemetryPreflight.dataSource.route must use a selected MCP server"
|
|
13176
13240
|
});
|
|
13177
13241
|
}
|
|
13178
13242
|
if (route.kind === "first_party" && !selectedFirstPartyTools.has(route.tool)) {
|
|
13179
|
-
throw new
|
|
13243
|
+
throw new HTTPException21(422, {
|
|
13180
13244
|
message: "incidentTelemetryPreflight.dataSource.route must use a selected first-party tool"
|
|
13181
13245
|
});
|
|
13182
13246
|
}
|
|
@@ -13184,14 +13248,14 @@ function validateIncidentTelemetryPreflightSelection(settings, agentConfig) {
|
|
|
13184
13248
|
const declaredSets = new Set(preflight.requiredVariableSetNames);
|
|
13185
13249
|
const declaredVariables = new Set(preflight.requiredVariableNames);
|
|
13186
13250
|
if (!declaredSets.has(route.variableSetName) || route.variableNames.some((name) => !declaredVariables.has(name))) {
|
|
13187
|
-
throw new
|
|
13251
|
+
throw new HTTPException21(422, {
|
|
13188
13252
|
message: "incidentTelemetryPreflight.dataSource.route variable metadata must be declared as required"
|
|
13189
13253
|
});
|
|
13190
13254
|
}
|
|
13191
13255
|
}
|
|
13192
13256
|
if (route.kind === "rig_credential_hook") {
|
|
13193
13257
|
if (!preflight.requiredRig?.credentialHookIds.includes(route.credentialHookId)) {
|
|
13194
|
-
throw new
|
|
13258
|
+
throw new HTTPException21(422, {
|
|
13195
13259
|
message: "incidentTelemetryPreflight.dataSource.route rig hook must be declared as required"
|
|
13196
13260
|
});
|
|
13197
13261
|
}
|
|
@@ -13202,13 +13266,13 @@ function validateScheduledTaskSchedule(schedule) {
|
|
|
13202
13266
|
return;
|
|
13203
13267
|
}
|
|
13204
13268
|
if (new Date(schedule.startAt).getTime() >= new Date(schedule.endAt).getTime()) {
|
|
13205
|
-
throw new
|
|
13269
|
+
throw new HTTPException21(422, { message: "interval schedule endAt must be after startAt" });
|
|
13206
13270
|
}
|
|
13207
13271
|
}
|
|
13208
13272
|
function trimmedScheduledTaskName(name) {
|
|
13209
13273
|
const trimmed = name.trim();
|
|
13210
13274
|
if (!trimmed) {
|
|
13211
|
-
throw new
|
|
13275
|
+
throw new HTTPException21(422, { message: "scheduled task name is required" });
|
|
13212
13276
|
}
|
|
13213
13277
|
return trimmed;
|
|
13214
13278
|
}
|
|
@@ -15818,7 +15882,7 @@ function providerReceiptWire(receipt2) {
|
|
|
15818
15882
|
}
|
|
15819
15883
|
|
|
15820
15884
|
// src/domain/workspace-members.ts
|
|
15821
|
-
import { HTTPException as
|
|
15885
|
+
import { HTTPException as HTTPException22 } from "hono/http-exception";
|
|
15822
15886
|
var MEMBER_ADMIN_PERMISSIONS = ["workspace:admin", "members:manage"];
|
|
15823
15887
|
function memberCanAdminister(member) {
|
|
15824
15888
|
return member.permissions.some((permission) => MEMBER_ADMIN_PERMISSIONS.includes(permission));
|
|
@@ -15828,27 +15892,27 @@ function isUserMember(member) {
|
|
|
15828
15892
|
}
|
|
15829
15893
|
function resolveMemberSubjectId(userId) {
|
|
15830
15894
|
if (!userId) {
|
|
15831
|
-
throw new
|
|
15895
|
+
throw new HTTPException22(404, { message: "user is not registered" });
|
|
15832
15896
|
}
|
|
15833
15897
|
return `user:${userId}`;
|
|
15834
15898
|
}
|
|
15835
15899
|
function assertWorkspaceMemberRemovable(input) {
|
|
15836
15900
|
const { members, subjectId, callerSubjectId } = input;
|
|
15837
15901
|
if (subjectId === callerSubjectId) {
|
|
15838
|
-
throw new
|
|
15902
|
+
throw new HTTPException22(409, {
|
|
15839
15903
|
message: "you cannot remove your own membership"
|
|
15840
15904
|
});
|
|
15841
15905
|
}
|
|
15842
15906
|
const target = members.find((member) => member.subjectId === subjectId);
|
|
15843
15907
|
if (!target) {
|
|
15844
|
-
throw new
|
|
15908
|
+
throw new HTTPException22(404, { message: "member not found" });
|
|
15845
15909
|
}
|
|
15846
15910
|
if (memberCanAdminister(target)) {
|
|
15847
15911
|
const remainingAdmins = members.filter(
|
|
15848
15912
|
(member) => member.subjectId !== subjectId && memberCanAdminister(member)
|
|
15849
15913
|
);
|
|
15850
15914
|
if (remainingAdmins.length === 0) {
|
|
15851
|
-
throw new
|
|
15915
|
+
throw new HTTPException22(409, {
|
|
15852
15916
|
message: "cannot remove the last member who can manage this workspace"
|
|
15853
15917
|
});
|
|
15854
15918
|
}
|
|
@@ -15857,28 +15921,28 @@ function assertWorkspaceMemberRemovable(input) {
|
|
|
15857
15921
|
function assertWorkspaceMemberUpdateAllowed(input) {
|
|
15858
15922
|
const { members, subjectId, callerSubjectId, nextPermissions } = input;
|
|
15859
15923
|
if (subjectId === callerSubjectId) {
|
|
15860
|
-
throw new
|
|
15924
|
+
throw new HTTPException22(409, {
|
|
15861
15925
|
message: "you cannot change your own workspace access"
|
|
15862
15926
|
});
|
|
15863
15927
|
}
|
|
15864
15928
|
const target = members.find((member) => member.subjectId === subjectId);
|
|
15865
15929
|
if (!target) {
|
|
15866
|
-
throw new
|
|
15930
|
+
throw new HTTPException22(404, { message: "member not found" });
|
|
15867
15931
|
}
|
|
15868
15932
|
if (memberCanAdminister(target) && !memberCanAdminister({ permissions: nextPermissions }) && !members.some((member) => member.subjectId !== subjectId && memberCanAdminister(member))) {
|
|
15869
|
-
throw new
|
|
15933
|
+
throw new HTTPException22(409, {
|
|
15870
15934
|
message: "the workspace must keep at least one administrator"
|
|
15871
15935
|
});
|
|
15872
15936
|
}
|
|
15873
15937
|
}
|
|
15874
15938
|
function assertWorkspaceDeletable(input) {
|
|
15875
15939
|
if (input.workspaceCountForAccount <= 1) {
|
|
15876
|
-
throw new
|
|
15940
|
+
throw new HTTPException22(409, {
|
|
15877
15941
|
message: "cannot delete the account's only workspace"
|
|
15878
15942
|
});
|
|
15879
15943
|
}
|
|
15880
15944
|
if (input.activeSessionCount > 0) {
|
|
15881
|
-
throw new
|
|
15945
|
+
throw new HTTPException22(409, {
|
|
15882
15946
|
message: "stop the workspace's running sessions before deleting it"
|
|
15883
15947
|
});
|
|
15884
15948
|
}
|
|
@@ -16146,7 +16210,7 @@ import {
|
|
|
16146
16210
|
saveNewSessionDraftInTransaction,
|
|
16147
16211
|
withWorkspaceSubjectRls as withWorkspaceSubjectRls6
|
|
16148
16212
|
} from "@opengeni/db";
|
|
16149
|
-
import { HTTPException as
|
|
16213
|
+
import { HTTPException as HTTPException23 } from "hono/http-exception";
|
|
16150
16214
|
function hasOwn(value, key) {
|
|
16151
16215
|
return typeof value === "object" && value !== null && Object.hasOwn(value, key);
|
|
16152
16216
|
}
|
|
@@ -16298,7 +16362,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput, cano
|
|
|
16298
16362
|
const tools = toolsProvided ? validateToolRefs(input.tools, runtimeSettings) : [];
|
|
16299
16363
|
await validateGitHubRepositorySelection(deps.db, workspaceId, resources);
|
|
16300
16364
|
if (resources.some((resource) => resource.kind === "file") && !deps.objectStorage) {
|
|
16301
|
-
throw new
|
|
16365
|
+
throw new HTTPException23(503, {
|
|
16302
16366
|
message: "object storage is not configured"
|
|
16303
16367
|
});
|
|
16304
16368
|
}
|
|
@@ -16341,7 +16405,7 @@ async function saveActorNewSessionDraft(deps, grant, workspaceId, rawInput, cano
|
|
|
16341
16405
|
return mapNewSessionDraft(saved);
|
|
16342
16406
|
} catch (error) {
|
|
16343
16407
|
if (error instanceof NewSessionDraftAccessError) {
|
|
16344
|
-
throw new
|
|
16408
|
+
throw new HTTPException23(403, { message: error.message });
|
|
16345
16409
|
}
|
|
16346
16410
|
throw error;
|
|
16347
16411
|
}
|