@opengeni/core 2.8.0-canary.1 → 2.8.2-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dependencies.d.ts +4 -2
- package/dist/domain/github-skill-source.d.ts +5 -0
- package/dist/domain/packs.d.ts +2 -3
- package/dist/domain/pr-review.d.ts +2 -1
- package/dist/domain/remember.d.ts +2 -2
- package/dist/domain/sessions.d.ts +1 -0
- package/dist/domain/skill-search.d.ts +30 -0
- package/dist/domain/skills.d.ts +73 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +522 -30
- package/dist/index.js.map +1 -1
- package/dist/transcription.d.ts +12 -1
- package/package.json +12 -12
- package/src/dependencies.ts +4 -2
- package/src/domain/github-skill-source.ts +142 -0
- package/src/domain/packs.ts +16 -8
- package/src/domain/product-integration-pack.ts +1 -0
- package/src/domain/remember.ts +7 -0
- package/src/domain/scheduled-tasks.ts +28 -0
- package/src/domain/sessions.ts +17 -0
- package/src/domain/skill-imports.ts +99 -15
- package/src/domain/skill-search.ts +218 -0
- package/src/domain/skills.ts +87 -0
- package/src/index.ts +3 -0
- package/src/sandbox/routing.ts +40 -1
- package/src/sandbox/runtime-settings.ts +9 -2
- package/src/transcription.ts +13 -1
package/dist/index.js
CHANGED
|
@@ -128,6 +128,73 @@ import {
|
|
|
128
128
|
validateEditableArtifactActor
|
|
129
129
|
} from "./chunk-VCZFFEPB.js";
|
|
130
130
|
|
|
131
|
+
// src/domain/skills.ts
|
|
132
|
+
import { validateSkillTextFiles } from "@opengeni/contracts";
|
|
133
|
+
import { buildPortableSkillArtifact } from "@opengeni/runtime/skill-library";
|
|
134
|
+
import {
|
|
135
|
+
applySkillLifecycle,
|
|
136
|
+
listSkillRecords,
|
|
137
|
+
skillFilesContentHash
|
|
138
|
+
} from "@opengeni/db";
|
|
139
|
+
import { replayPortableSkillInstall } from "@opengeni/db";
|
|
140
|
+
function validateSkillFiles(files) {
|
|
141
|
+
validateSkillTextFiles(files);
|
|
142
|
+
const result = files.map(({ path, content }) => ({ path, content }));
|
|
143
|
+
if (!result.some((file) => file.path === "SKILL.md" && file.content.trim()))
|
|
144
|
+
throw new Error("Skill requires nonempty SKILL.md");
|
|
145
|
+
return result.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
146
|
+
}
|
|
147
|
+
function skillBundleHash(files) {
|
|
148
|
+
return skillFilesContentHash(validateSkillFiles(files));
|
|
149
|
+
}
|
|
150
|
+
var listSkills = listSkillRecords;
|
|
151
|
+
async function readSkill(db, context, skillId, revisionId) {
|
|
152
|
+
return (await listSkillRecords(db, context, {
|
|
153
|
+
skillId,
|
|
154
|
+
...revisionId ? { revisionId } : {},
|
|
155
|
+
limit: 1
|
|
156
|
+
}))[0] ?? null;
|
|
157
|
+
}
|
|
158
|
+
async function saveSkill(db, input) {
|
|
159
|
+
const { accountId, workspaceId, actor, ...request } = input;
|
|
160
|
+
const artifact = buildPortableSkillArtifact(validateSkillFiles(input.files));
|
|
161
|
+
return applySkillLifecycle(
|
|
162
|
+
db,
|
|
163
|
+
{ accountId, workspaceId, actor },
|
|
164
|
+
{
|
|
165
|
+
...request,
|
|
166
|
+
operation: "save",
|
|
167
|
+
files: artifact.files,
|
|
168
|
+
title: artifact.name,
|
|
169
|
+
description: artifact.description
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
async function installSkill(db, input) {
|
|
174
|
+
const { accountId, workspaceId, actor, ...request } = input;
|
|
175
|
+
return applySkillLifecycle(
|
|
176
|
+
db,
|
|
177
|
+
{ accountId, workspaceId, actor },
|
|
178
|
+
{ ...request, operation: "install" }
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
async function approveSkill(db, input) {
|
|
182
|
+
const { accountId, workspaceId, actor, ...request } = input;
|
|
183
|
+
return applySkillLifecycle(
|
|
184
|
+
db,
|
|
185
|
+
{ accountId, workspaceId, actor },
|
|
186
|
+
{ ...request, operation: "approve" }
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
async function restoreSkill(db, input) {
|
|
190
|
+
const { accountId, workspaceId, actor, ...request } = input;
|
|
191
|
+
return applySkillLifecycle(
|
|
192
|
+
db,
|
|
193
|
+
{ accountId, workspaceId, actor },
|
|
194
|
+
{ ...request, operation: "restore" }
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
131
198
|
// src/workflow-wake-contract.ts
|
|
132
199
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wake-dispatcher";
|
|
133
200
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
|
|
@@ -140,12 +207,15 @@ var TranscriptionServiceError = class extends Error {
|
|
|
140
207
|
code;
|
|
141
208
|
status;
|
|
142
209
|
retryable;
|
|
210
|
+
/** Explicit rejection before any transcription result; safe to try another provider. */
|
|
211
|
+
fallbackSafe;
|
|
143
212
|
constructor(input) {
|
|
144
213
|
super(input.message);
|
|
145
214
|
this.name = "TranscriptionServiceError";
|
|
146
215
|
this.code = input.code;
|
|
147
216
|
this.status = input.status ?? statusForVoiceInputError(input.code);
|
|
148
217
|
this.retryable = input.retryable ?? false;
|
|
218
|
+
this.fallbackSafe = input.fallbackSafe ?? false;
|
|
149
219
|
}
|
|
150
220
|
};
|
|
151
221
|
function statusForVoiceInputError(code) {
|
|
@@ -718,6 +788,11 @@ import {
|
|
|
718
788
|
|
|
719
789
|
// src/sandbox/routing.ts
|
|
720
790
|
import { sandboxLifecycleTransitionWaitMs } from "@opengeni/config";
|
|
791
|
+
import { appendSessionCommandOutput } from "@opengeni/db/session-command-output";
|
|
792
|
+
import {
|
|
793
|
+
createProviderCommandRetainer,
|
|
794
|
+
retainedProviderCommandPersistence
|
|
795
|
+
} from "@opengeni/db/retained-provider-commands";
|
|
721
796
|
import {
|
|
722
797
|
advanceWorkspaceGenerationForDirectRequest,
|
|
723
798
|
advanceWorkspaceGenerationForRetainedProcess,
|
|
@@ -727,6 +802,7 @@ import {
|
|
|
727
802
|
markWarmLeaseInstanceLost,
|
|
728
803
|
readActiveSandbox,
|
|
729
804
|
retainWorkspaceMutationProcess,
|
|
805
|
+
SandboxRetainedProcessPromotionFencedError,
|
|
730
806
|
retainedProcessSettlementIdentity,
|
|
731
807
|
settleRetainedProcess,
|
|
732
808
|
verifyDirectWorkspaceMutationSettlement,
|
|
@@ -741,6 +817,10 @@ import {
|
|
|
741
817
|
RoutingSandboxSession,
|
|
742
818
|
resolveModalCheckpointProviderBindingForSession
|
|
743
819
|
} from "@opengeni/runtime/sandbox";
|
|
820
|
+
var retainWorkspaceProviderCommand = createProviderCommandRetainer(
|
|
821
|
+
retainWorkspaceMutationProcess,
|
|
822
|
+
(error) => error instanceof SandboxRetainedProcessPromotionFencedError ? error.process : null
|
|
823
|
+
);
|
|
744
824
|
function directRetainedProcessMatchesBackend(durable, process, backend) {
|
|
745
825
|
return durable.providerSessionId === process.providerSessionId && durable.providerBackend === backend.kind && durable.providerInstanceId === backend.providerInstanceId && durable.leaseEpoch === backend.leaseEpoch && durable.routeKind === "active" && durable.routeTargetId === backend.sandboxId && durable.routeEpoch === backend.activeEpoch;
|
|
746
826
|
}
|
|
@@ -860,12 +940,13 @@ function wrapChannelABoxWithRouting(services, ids, established) {
|
|
|
860
940
|
throw new Error("API-direct workspace mutation settlement lacked its bound admission");
|
|
861
941
|
}
|
|
862
942
|
if (outcome === "resolved" && retainedProcess) {
|
|
863
|
-
await
|
|
943
|
+
await retainWorkspaceProviderCommand(db, {
|
|
864
944
|
accountId: ids.accountId,
|
|
865
945
|
workspaceId: ids.workspaceId,
|
|
866
946
|
sessionId: ids.sessionId,
|
|
867
947
|
processId: retainedProcess.id,
|
|
868
948
|
providerSessionId: retainedProcess.providerSessionId,
|
|
949
|
+
...retainedProcess.providerCommand ? { providerCommand: retainedProcess.providerCommand } : {},
|
|
869
950
|
admissionId: exactAdmission.id,
|
|
870
951
|
admittedWorkspaceGeneration: exactAdmission.workspaceGeneration,
|
|
871
952
|
operation: op,
|
|
@@ -1022,6 +1103,27 @@ function wrapChannelABoxWithRouting(services, ids, established) {
|
|
|
1022
1103
|
} : {}
|
|
1023
1104
|
});
|
|
1024
1105
|
const proxy = new RoutingSandboxSession({
|
|
1106
|
+
providerCommandHandle: (value) => value && typeof value === "object" ? value.admission?.workspaceGeneration : void 0,
|
|
1107
|
+
providerCommandPersistence: (process) => retainedProviderCommandPersistence(db, {
|
|
1108
|
+
accountId: ids.accountId,
|
|
1109
|
+
workspaceId: ids.workspaceId,
|
|
1110
|
+
sessionId: ids.sessionId,
|
|
1111
|
+
processId: process.id
|
|
1112
|
+
}),
|
|
1113
|
+
captureProcessOutput: async ({ process, chunkId, chunk, stream, streamFidelity }) => {
|
|
1114
|
+
const events = await appendSessionCommandOutput(db, {
|
|
1115
|
+
accountId: ids.accountId,
|
|
1116
|
+
workspaceId: ids.workspaceId,
|
|
1117
|
+
sessionId: ids.sessionId,
|
|
1118
|
+
commandId: process.id,
|
|
1119
|
+
chunkId,
|
|
1120
|
+
chunk,
|
|
1121
|
+
stream,
|
|
1122
|
+
streamFidelity
|
|
1123
|
+
});
|
|
1124
|
+
if (events.length && bus)
|
|
1125
|
+
await bus.publish(ids.workspaceId, ids.sessionId, events).catch(() => void 0);
|
|
1126
|
+
},
|
|
1025
1127
|
bindActiveRouteOnFirstResolve: true,
|
|
1026
1128
|
defaultResolved: {
|
|
1027
1129
|
session: established.session,
|
|
@@ -1079,7 +1181,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
|
|
|
1079
1181
|
|
|
1080
1182
|
// src/sandbox/runtime-settings.ts
|
|
1081
1183
|
import {
|
|
1082
|
-
|
|
1184
|
+
StoredCapabilityPack
|
|
1083
1185
|
} from "@opengeni/contracts";
|
|
1084
1186
|
import {
|
|
1085
1187
|
getRigVersion,
|
|
@@ -1157,8 +1259,14 @@ async function resolveWorkspaceLegacyRuntimePacks(db, workspaceId) {
|
|
|
1157
1259
|
continue;
|
|
1158
1260
|
}
|
|
1159
1261
|
const registration = await getWorkspacePack(db, workspaceId, installation.packId);
|
|
1160
|
-
|
|
1161
|
-
|
|
1262
|
+
if (!registration)
|
|
1263
|
+
throw new Error(`Enabled Pack ${installation.packId} has no registered manifest`);
|
|
1264
|
+
const parsed = StoredCapabilityPack.safeParse(registration.pack);
|
|
1265
|
+
if (!parsed.success)
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
`Enabled Pack ${installation.packId} requires repair: ${parsed.error.message}`
|
|
1268
|
+
);
|
|
1269
|
+
packs2.push(parsed.data);
|
|
1162
1270
|
}
|
|
1163
1271
|
return packs2;
|
|
1164
1272
|
}
|
|
@@ -2939,7 +3047,7 @@ import { listSkillLibraryEntries } from "@opengeni/runtime/skill-library";
|
|
|
2939
3047
|
// src/domain/packs.ts
|
|
2940
3048
|
import { createHash as createHash3 } from "crypto";
|
|
2941
3049
|
import {
|
|
2942
|
-
|
|
3050
|
+
StoredCapabilityPack as StoredCapabilityPack2,
|
|
2943
3051
|
OPENGENI_PR_REVIEW_PACK_ID,
|
|
2944
3052
|
OPENGENI_PR_REVIEW_SESSION_ROLE as OPENGENI_PR_REVIEW_SESSION_ROLE2,
|
|
2945
3053
|
stableJson as stableJson2
|
|
@@ -2954,7 +3062,7 @@ import {
|
|
|
2954
3062
|
resolvePackComponentReferences,
|
|
2955
3063
|
resolvePackInlineSkillReferences
|
|
2956
3064
|
} from "@opengeni/db";
|
|
2957
|
-
import { buildPortableSkillArtifact } from "@opengeni/runtime/skill-library";
|
|
3065
|
+
import { buildPortableSkillArtifact as buildPortableSkillArtifact2 } from "@opengeni/runtime/skill-library";
|
|
2958
3066
|
import { HTTPException as HTTPException3 } from "hono/http-exception";
|
|
2959
3067
|
|
|
2960
3068
|
// src/domain/pr-review.ts
|
|
@@ -3486,6 +3594,7 @@ The desired outcome is a native-feeling product experience backed by a standalon
|
|
|
3486
3594
|
- When an unknown choice is reversible and low-risk, choose the best-fitting default, state the assumption, and continue. When it changes privacy, tenant authority, write access, cost exposure, or an external mutation, resolve it before crossing that boundary.
|
|
3487
3595
|
- Possession of a credential or access to a cloud, repository, or deployment is technical capability, not authorization. Match the user's requested delivery autonomy and the repository's stated workflow.
|
|
3488
3596
|
- Keep alternatives open until evidence eliminates them. Use strict rules only for actual security, privacy, protocol, or authorization invariants.
|
|
3597
|
+
- For packaged React chat, use SessionConversation or compose MessageTimeline and ChatComposer with the normal SDK and authenticated session routes. For a custom or compatible frontend, use the optional backend chat handler.
|
|
3489
3598
|
|
|
3490
3599
|
Read the references selectively:
|
|
3491
3600
|
|
|
@@ -4197,9 +4306,13 @@ function isBuiltInCapabilityPack(packId) {
|
|
|
4197
4306
|
async function listWorkspaceCapabilityPacks(db, workspaceId) {
|
|
4198
4307
|
const registered = await listWorkspacePacks(db, workspaceId);
|
|
4199
4308
|
const builtInIds = new Set(packs.map((pack) => pack.id));
|
|
4200
|
-
const registeredPacks = registered.filter((registration) => !builtInIds.has(registration.pack.id)).
|
|
4201
|
-
const parsed =
|
|
4202
|
-
|
|
4309
|
+
const registeredPacks = registered.filter((registration) => !builtInIds.has(registration.pack.id)).map((registration) => {
|
|
4310
|
+
const parsed = StoredCapabilityPack2.safeParse(registration.pack);
|
|
4311
|
+
if (!parsed.success)
|
|
4312
|
+
throw new HTTPException3(422, {
|
|
4313
|
+
message: `Stored Pack ${registration.pack.id} requires repair before it can be used: ${parsed.error.message}`
|
|
4314
|
+
});
|
|
4315
|
+
return parsed.data;
|
|
4203
4316
|
});
|
|
4204
4317
|
return [...packs, ...registeredPacks];
|
|
4205
4318
|
}
|
|
@@ -4212,8 +4325,12 @@ async function resolveCapabilityPack(db, workspaceId, packId) {
|
|
|
4212
4325
|
if (!registration) {
|
|
4213
4326
|
return null;
|
|
4214
4327
|
}
|
|
4215
|
-
const parsed =
|
|
4216
|
-
|
|
4328
|
+
const parsed = StoredCapabilityPack2.safeParse(registration.pack);
|
|
4329
|
+
if (!parsed.success)
|
|
4330
|
+
throw new HTTPException3(422, {
|
|
4331
|
+
message: `Stored Pack ${packId} requires repair before it can be used: ${parsed.error.message}`
|
|
4332
|
+
});
|
|
4333
|
+
return parsed.data;
|
|
4217
4334
|
}
|
|
4218
4335
|
function capabilityPackManifestDigest(pack) {
|
|
4219
4336
|
return createHash3("sha256").update(stableJson2(pack)).digest("hex");
|
|
@@ -4222,7 +4339,7 @@ function capabilityPackRequiresInstallationPlan(pack) {
|
|
|
4222
4339
|
return pack.components.length > 0 || pack.skills.length > 0 || (pack.automationTemplates?.length ?? 0) > 0 || pack.rig !== void 0 || pack.sandboxImage !== void 0 || pack.sandboxProviderImages !== void 0;
|
|
4223
4340
|
}
|
|
4224
4341
|
function inlinePackSkillInstall(pack, skill) {
|
|
4225
|
-
const artifact =
|
|
4342
|
+
const artifact = buildPortableSkillArtifact2(skill.files);
|
|
4226
4343
|
if (artifact.name.toLowerCase() !== skill.name.toLowerCase()) {
|
|
4227
4344
|
throw new HTTPException3(422, {
|
|
4228
4345
|
message: `Pack Skill ${skill.name} has SKILL.md name ${artifact.name}; the names must match`
|
|
@@ -4919,13 +5036,13 @@ async function probeStreamableHttpMcpServer(input) {
|
|
|
4919
5036
|
function mcpProbeErrorMessage(error, endpointUrl) {
|
|
4920
5037
|
const message = error instanceof Error ? error.message : String(error);
|
|
4921
5038
|
const normalized = message.replace(/\s+/g, " ").trim();
|
|
4922
|
-
const
|
|
5039
|
+
const endpoint2 = safeEndpointLabel(endpointUrl);
|
|
4923
5040
|
if (/404|405|not found|unexpected token|not valid json|invalid json|failed to parse|streamable http error|unable to connect|fetch failed|econnrefused|enotfound|timeout|aborted/i.test(
|
|
4924
5041
|
normalized
|
|
4925
5042
|
)) {
|
|
4926
|
-
return `OpenGeni could not reach a valid Streamable HTTP MCP server at ${
|
|
5043
|
+
return `OpenGeni could not reach a valid Streamable HTTP MCP server at ${endpoint2}. Check the endpoint URL or choose a different catalog entry.`;
|
|
4927
5044
|
}
|
|
4928
|
-
return `OpenGeni could not initialize ${
|
|
5045
|
+
return `OpenGeni could not initialize ${endpoint2}. Check the endpoint configuration or try again.`;
|
|
4929
5046
|
}
|
|
4930
5047
|
function safeEndpointLabel(endpointUrl) {
|
|
4931
5048
|
try {
|
|
@@ -5920,7 +6037,9 @@ function storedConnectionRef(config) {
|
|
|
5920
6037
|
// src/domain/skill-imports.ts
|
|
5921
6038
|
import { createHash as createHash4 } from "crypto";
|
|
5922
6039
|
import {
|
|
5923
|
-
buildPortableSkillArtifact as
|
|
6040
|
+
buildPortableSkillArtifact as buildPortableSkillArtifact3,
|
|
6041
|
+
parsePortableSkillFrontmatter,
|
|
6042
|
+
PORTABLE_SKILL_MAX_FILE_BYTES,
|
|
5924
6043
|
PORTABLE_SKILL_MAX_FILES,
|
|
5925
6044
|
PORTABLE_SKILL_MAX_TOTAL_BYTES
|
|
5926
6045
|
} from "@opengeni/runtime/skill-library";
|
|
@@ -5935,7 +6054,16 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
5935
6054
|
throw new HTTPException6(422, { message: "GitHub returned an invalid source commit" });
|
|
5936
6055
|
}
|
|
5937
6056
|
const tree = await client.listTree(parsed.owner, parsed.repository, sourceCommit);
|
|
5938
|
-
const
|
|
6057
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
6058
|
+
const readBlob = (sha) => {
|
|
6059
|
+
let pending = blobs.get(sha);
|
|
6060
|
+
if (!pending) {
|
|
6061
|
+
pending = client.readBlob(parsed.owner, parsed.repository, sha);
|
|
6062
|
+
blobs.set(sha, pending);
|
|
6063
|
+
}
|
|
6064
|
+
return pending;
|
|
6065
|
+
};
|
|
6066
|
+
const sourcePath = await selectSkillRoot(parsed, tree, readBlob, sourceCommit);
|
|
5939
6067
|
const entries = skillFilesUnderRoot(tree, sourcePath);
|
|
5940
6068
|
const declaredBytes = entries.reduce((sum, entry) => sum + (entry.size ?? 0), 0);
|
|
5941
6069
|
if (entries.length > PORTABLE_SKILL_MAX_FILES) {
|
|
@@ -5949,11 +6077,10 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
5949
6077
|
});
|
|
5950
6078
|
}
|
|
5951
6079
|
const files = await mapConcurrent(entries, maxConcurrentBlobReads, async (entry) => {
|
|
6080
|
+
const bytes = await readBlob(entry.sha);
|
|
5952
6081
|
let content;
|
|
5953
6082
|
try {
|
|
5954
|
-
content = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
5955
|
-
await client.readBlob(parsed.owner, parsed.repository, entry.sha)
|
|
5956
|
-
);
|
|
6083
|
+
content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
5957
6084
|
} catch {
|
|
5958
6085
|
throw new HTTPException6(422, {
|
|
5959
6086
|
message: `Skill file is not valid UTF-8 text: ${relativeSkillPath(entry.path, sourcePath)}`
|
|
@@ -5963,7 +6090,7 @@ async function resolveSkillImport(rawUrl, client) {
|
|
|
5963
6090
|
});
|
|
5964
6091
|
let artifact;
|
|
5965
6092
|
try {
|
|
5966
|
-
artifact =
|
|
6093
|
+
artifact = buildPortableSkillArtifact3(files);
|
|
5967
6094
|
} catch (error) {
|
|
5968
6095
|
throw new HTTPException6(422, {
|
|
5969
6096
|
message: error instanceof Error ? error.message : "Skill artifact is invalid"
|
|
@@ -6029,7 +6156,12 @@ function parseSkillSource(rawUrl) {
|
|
|
6029
6156
|
message: "Skill imports require a credential-free HTTPS URL without a fragment"
|
|
6030
6157
|
});
|
|
6031
6158
|
}
|
|
6032
|
-
|
|
6159
|
+
let segments;
|
|
6160
|
+
try {
|
|
6161
|
+
segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
6162
|
+
} catch {
|
|
6163
|
+
throw new HTTPException6(422, { message: "The Skill URL contains invalid encoding" });
|
|
6164
|
+
}
|
|
6033
6165
|
if (url.hostname === "skills.sh" || url.hostname === "www.skills.sh") {
|
|
6034
6166
|
if (segments.length !== 3) {
|
|
6035
6167
|
throw new HTTPException6(422, {
|
|
@@ -6082,12 +6214,11 @@ function parseSkillSource(rawUrl) {
|
|
|
6082
6214
|
const ref = segments[3];
|
|
6083
6215
|
if (!ref) throw new HTTPException6(422, { message: "The GitHub URL is missing a revision" });
|
|
6084
6216
|
const pathSegments = segments.slice(4);
|
|
6085
|
-
if (pathSegments.length === 0) {
|
|
6217
|
+
if (pathSegments.length === 0 && mode === "blob") {
|
|
6086
6218
|
throw new HTTPException6(422, { message: "The GitHub URL is missing a Skill folder" });
|
|
6087
6219
|
}
|
|
6088
|
-
const
|
|
6089
|
-
|
|
6090
|
-
);
|
|
6220
|
+
const folderSegments = mode === "blob" && pathSegments.at(-1)?.toLowerCase() === "skill.md" ? pathSegments.slice(0, -1) : pathSegments;
|
|
6221
|
+
const requestedPath = folderSegments.length === 0 ? "." : normalizeGitHubPath(folderSegments);
|
|
6091
6222
|
return {
|
|
6092
6223
|
source: "github",
|
|
6093
6224
|
owner,
|
|
@@ -6098,10 +6229,10 @@ function parseSkillSource(rawUrl) {
|
|
|
6098
6229
|
inputUrl: url.toString()
|
|
6099
6230
|
};
|
|
6100
6231
|
}
|
|
6101
|
-
function selectSkillRoot(source, tree) {
|
|
6232
|
+
async function selectSkillRoot(source, tree, readBlob, sourceCommit) {
|
|
6102
6233
|
const skillFiles = tree.filter(
|
|
6103
6234
|
(entry) => entry.type === "blob" && entry.mode !== "120000" && (entry.path === "SKILL.md" || entry.path.endsWith("/SKILL.md"))
|
|
6104
|
-
).map((entry) => entry.path.slice(0, -"/SKILL.md".length)
|
|
6235
|
+
).map((entry) => entry.path === "SKILL.md" ? "." : entry.path.slice(0, -"/SKILL.md".length)).sort();
|
|
6105
6236
|
if (source.requestedPath) {
|
|
6106
6237
|
const root = source.requestedPath;
|
|
6107
6238
|
if (!skillFiles.includes(root)) {
|
|
@@ -6111,7 +6242,48 @@ function selectSkillRoot(source, tree) {
|
|
|
6111
6242
|
}
|
|
6112
6243
|
return root;
|
|
6113
6244
|
}
|
|
6114
|
-
|
|
6245
|
+
let candidates = skillFiles;
|
|
6246
|
+
if (source.skillSlug) {
|
|
6247
|
+
if (skillFiles.length > PORTABLE_SKILL_MAX_FILES) {
|
|
6248
|
+
throw new HTTPException6(422, {
|
|
6249
|
+
message: "Too many Skill candidates; paste the exact GitHub folder URL"
|
|
6250
|
+
});
|
|
6251
|
+
}
|
|
6252
|
+
let metadataBytes = 0;
|
|
6253
|
+
const entriesByPath = new Map(tree.map((entry) => [entry.path, entry]));
|
|
6254
|
+
const matches = await mapConcurrent(skillFiles, maxConcurrentBlobReads, async (root) => {
|
|
6255
|
+
const entry = entriesByPath.get(root === "." ? "SKILL.md" : `${root}/SKILL.md`);
|
|
6256
|
+
if ((entry.size ?? 0) > PORTABLE_SKILL_MAX_FILE_BYTES) {
|
|
6257
|
+
throw new HTTPException6(422, {
|
|
6258
|
+
message: "Skill metadata is too large; paste the exact GitHub folder URL"
|
|
6259
|
+
});
|
|
6260
|
+
}
|
|
6261
|
+
const bytes = await readBlob(entry.sha);
|
|
6262
|
+
metadataBytes += bytes.byteLength;
|
|
6263
|
+
if (bytes.byteLength > PORTABLE_SKILL_MAX_FILE_BYTES || metadataBytes > PORTABLE_SKILL_MAX_TOTAL_BYTES) {
|
|
6264
|
+
throw new HTTPException6(422, {
|
|
6265
|
+
message: "Skill metadata is too large; paste the exact GitHub folder URL"
|
|
6266
|
+
});
|
|
6267
|
+
}
|
|
6268
|
+
let markdown;
|
|
6269
|
+
try {
|
|
6270
|
+
markdown = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
6271
|
+
} catch {
|
|
6272
|
+
throw new HTTPException6(422, {
|
|
6273
|
+
message: "Skill metadata is not valid UTF-8; paste the exact GitHub folder URL"
|
|
6274
|
+
});
|
|
6275
|
+
}
|
|
6276
|
+
return parsePortableSkillFrontmatter(markdown).name?.toLowerCase() === source.skillSlug.toLowerCase();
|
|
6277
|
+
});
|
|
6278
|
+
candidates = skillFiles.filter((_, index) => matches[index]);
|
|
6279
|
+
if (candidates.length === 0) {
|
|
6280
|
+
const folders = skillFiles.filter((path) => path.split("/").at(-1) === source.skillSlug);
|
|
6281
|
+
const exactPath = folders.length === 1 ? encodeGitHubPath(folders[0]) : "<exact-skill-folder-path>";
|
|
6282
|
+
throw new HTTPException6(422, {
|
|
6283
|
+
message: `No Skill frontmatter name matches skills.sh slug "${source.skillSlug}"; the link may be stale. Check the current Skill name, or explicitly select the intended Skill using its exact GitHub folder URL: https://github.com/${source.owner}/${source.repository}/tree/${sourceCommit}/${exactPath}`
|
|
6284
|
+
});
|
|
6285
|
+
}
|
|
6286
|
+
}
|
|
6115
6287
|
if (candidates.length === 0) {
|
|
6116
6288
|
throw new HTTPException6(422, { message: "No Skill folder with SKILL.md was found" });
|
|
6117
6289
|
}
|
|
@@ -6164,13 +6336,20 @@ function assertGitHubRepository(owner, repository) {
|
|
|
6164
6336
|
async function mapConcurrent(values, concurrency, map) {
|
|
6165
6337
|
const output = new Array(values.length);
|
|
6166
6338
|
let next = 0;
|
|
6339
|
+
let failed = false;
|
|
6167
6340
|
await Promise.all(
|
|
6168
6341
|
Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
6169
6342
|
for (; ; ) {
|
|
6343
|
+
if (failed) return;
|
|
6170
6344
|
const index = next;
|
|
6171
6345
|
next += 1;
|
|
6172
6346
|
if (index >= values.length) return;
|
|
6173
|
-
|
|
6347
|
+
try {
|
|
6348
|
+
output[index] = await map(values[index]);
|
|
6349
|
+
} catch (error) {
|
|
6350
|
+
failed = true;
|
|
6351
|
+
throw error;
|
|
6352
|
+
}
|
|
6174
6353
|
}
|
|
6175
6354
|
})
|
|
6176
6355
|
);
|
|
@@ -6180,6 +6359,260 @@ function sha256Hex2(bytes) {
|
|
|
6180
6359
|
return createHash4("sha256").update(bytes).digest("hex");
|
|
6181
6360
|
}
|
|
6182
6361
|
|
|
6362
|
+
// src/domain/skill-search.ts
|
|
6363
|
+
import { pinnedFetch, readResponseJsonBounded } from "@opengeni/network";
|
|
6364
|
+
var PublicSkillSearchError = class extends Error {
|
|
6365
|
+
constructor(code, message, retryAfterSeconds = null) {
|
|
6366
|
+
super(message);
|
|
6367
|
+
this.code = code;
|
|
6368
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
6369
|
+
this.name = "PublicSkillSearchError";
|
|
6370
|
+
}
|
|
6371
|
+
};
|
|
6372
|
+
var endpoint = "https://skills.sh/api/search";
|
|
6373
|
+
var timeoutMs = 1e4;
|
|
6374
|
+
var maxResponseBytes = 256 * 1024;
|
|
6375
|
+
var maxResults = 20;
|
|
6376
|
+
var ownerPattern = /^[a-z0-9](?:[a-z0-9-]{0,38})$/iu;
|
|
6377
|
+
var segmentPattern = /^[a-z0-9](?:[a-z0-9._-]{0,98}[a-z0-9])?$/iu;
|
|
6378
|
+
function createPublicSkillSearchClient(settings, fetcher = pinnedFetch) {
|
|
6379
|
+
return {
|
|
6380
|
+
async search(input) {
|
|
6381
|
+
const query = input.query.trim();
|
|
6382
|
+
const limit = input.limit ?? maxResults;
|
|
6383
|
+
const owner = input.owner?.trim().toLowerCase();
|
|
6384
|
+
if (query.length < 2 || query.length > 200 || /[\u0000-\u001f\u007f]/u.test(query) || !Number.isSafeInteger(limit) || limit < 1 || limit > maxResults || owner !== void 0 && !ownerPattern.test(owner)) {
|
|
6385
|
+
throw new PublicSkillSearchError("invalid_query", "Invalid public Skill search parameters");
|
|
6386
|
+
}
|
|
6387
|
+
const url = new URL(endpoint);
|
|
6388
|
+
url.searchParams.set("q", query);
|
|
6389
|
+
url.searchParams.set("limit", String(limit));
|
|
6390
|
+
if (owner) url.searchParams.set("owner", owner);
|
|
6391
|
+
const controller = new AbortController();
|
|
6392
|
+
let timer;
|
|
6393
|
+
const deadline = new Promise((_, reject) => {
|
|
6394
|
+
timer = setTimeout(() => {
|
|
6395
|
+
controller.abort();
|
|
6396
|
+
reject(new PublicSkillSearchError("timeout", "Public Skill search timed out"));
|
|
6397
|
+
}, timeoutMs);
|
|
6398
|
+
});
|
|
6399
|
+
try {
|
|
6400
|
+
return await Promise.race([
|
|
6401
|
+
deadline,
|
|
6402
|
+
(async () => {
|
|
6403
|
+
const response = await fetcher(
|
|
6404
|
+
url,
|
|
6405
|
+
{
|
|
6406
|
+
method: "GET",
|
|
6407
|
+
headers: { accept: "application/json" },
|
|
6408
|
+
credentials: "omit",
|
|
6409
|
+
redirect: "manual",
|
|
6410
|
+
signal: controller.signal
|
|
6411
|
+
},
|
|
6412
|
+
settings,
|
|
6413
|
+
{ label: "Public Skill search", requireHttpsOutsideLocalTest: true }
|
|
6414
|
+
);
|
|
6415
|
+
if (controller.signal.aborted || !response.ok) {
|
|
6416
|
+
void response.body?.cancel().catch(() => void 0);
|
|
6417
|
+
if (controller.signal.aborted) {
|
|
6418
|
+
throw new PublicSkillSearchError("timeout", "Public Skill search timed out");
|
|
6419
|
+
}
|
|
6420
|
+
if (response.status === 429) {
|
|
6421
|
+
throw new PublicSkillSearchError(
|
|
6422
|
+
"rate_limited",
|
|
6423
|
+
"Public Skill search is rate limited",
|
|
6424
|
+
parseRetryAfter(response.headers.get("retry-after"))
|
|
6425
|
+
);
|
|
6426
|
+
}
|
|
6427
|
+
throw new PublicSkillSearchError("unavailable", "Public Skill search is unavailable");
|
|
6428
|
+
}
|
|
6429
|
+
let payload;
|
|
6430
|
+
try {
|
|
6431
|
+
payload = await readResponseJsonBounded(
|
|
6432
|
+
response,
|
|
6433
|
+
maxResponseBytes,
|
|
6434
|
+
"Public Skill search",
|
|
6435
|
+
{
|
|
6436
|
+
signal: controller.signal
|
|
6437
|
+
}
|
|
6438
|
+
);
|
|
6439
|
+
} catch {
|
|
6440
|
+
throw new PublicSkillSearchError(
|
|
6441
|
+
controller.signal.aborted ? "timeout" : "invalid_response",
|
|
6442
|
+
controller.signal.aborted ? "Public Skill search timed out" : "Public Skill search returned an invalid response"
|
|
6443
|
+
);
|
|
6444
|
+
}
|
|
6445
|
+
return normalizeResponse(payload, query, limit, owner);
|
|
6446
|
+
})()
|
|
6447
|
+
]);
|
|
6448
|
+
} catch (error) {
|
|
6449
|
+
if (error instanceof PublicSkillSearchError) throw error;
|
|
6450
|
+
throw new PublicSkillSearchError(
|
|
6451
|
+
controller.signal.aborted ? "timeout" : "unavailable",
|
|
6452
|
+
controller.signal.aborted ? "Public Skill search timed out" : "Public Skill search is unavailable"
|
|
6453
|
+
);
|
|
6454
|
+
} finally {
|
|
6455
|
+
clearTimeout(timer);
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
};
|
|
6459
|
+
}
|
|
6460
|
+
function normalizeResponse(payload, query, limit, owner) {
|
|
6461
|
+
const invalid = () => new PublicSkillSearchError(
|
|
6462
|
+
"invalid_response",
|
|
6463
|
+
"Public Skill search returned an invalid response"
|
|
6464
|
+
);
|
|
6465
|
+
if (!isRecord2(payload) || !Array.isArray(payload.skills) || payload.skills.length > maxResults) {
|
|
6466
|
+
throw invalid();
|
|
6467
|
+
}
|
|
6468
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6469
|
+
const items = [];
|
|
6470
|
+
for (const skill of payload.skills) {
|
|
6471
|
+
if (!isRecord2(skill) || typeof skill.id !== "string" || typeof skill.source !== "string")
|
|
6472
|
+
throw invalid();
|
|
6473
|
+
const parts = skill.id.split("/");
|
|
6474
|
+
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)
|
|
6475
|
+
throw invalid();
|
|
6476
|
+
if (seen.has(skill.id)) continue;
|
|
6477
|
+
seen.add(skill.id);
|
|
6478
|
+
items.push({
|
|
6479
|
+
id: skill.id,
|
|
6480
|
+
name: skill.name.trim(),
|
|
6481
|
+
source: skill.source,
|
|
6482
|
+
skillId: parts[2],
|
|
6483
|
+
url: `https://skills.sh/${parts.map(encodeURIComponent).join("/")}`,
|
|
6484
|
+
installs: skill.installs
|
|
6485
|
+
});
|
|
6486
|
+
}
|
|
6487
|
+
return { provider: "skills_sh", query, items: items.slice(0, limit), nextCursor: null };
|
|
6488
|
+
}
|
|
6489
|
+
function isRecord2(value) {
|
|
6490
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6491
|
+
}
|
|
6492
|
+
function parseRetryAfter(value) {
|
|
6493
|
+
if (value === null) return null;
|
|
6494
|
+
if (/^\d+$/u.test(value)) {
|
|
6495
|
+
const seconds = Number(value);
|
|
6496
|
+
return Number.isSafeInteger(seconds) ? seconds : null;
|
|
6497
|
+
}
|
|
6498
|
+
const date = Date.parse(value);
|
|
6499
|
+
return Number.isFinite(date) ? Math.max(0, Math.ceil((date - Date.now()) / 1e3)) : null;
|
|
6500
|
+
}
|
|
6501
|
+
|
|
6502
|
+
// src/domain/github-skill-source.ts
|
|
6503
|
+
import { pinnedFetch as pinnedFetch2, readResponseJsonBounded as readResponseJsonBounded2, readResponseTextBounded } from "@opengeni/network";
|
|
6504
|
+
var githubApiBase = "https://api.github.com";
|
|
6505
|
+
var githubRequestTimeoutMs = 15e3;
|
|
6506
|
+
var githubMetadataMaxBytes = 4 * 1024 * 1024;
|
|
6507
|
+
var githubBlobResponseMaxBytes = 512 * 1024;
|
|
6508
|
+
function createGitHubSkillSourceClient(settings, requestJson = (path, maxBytes, label) => githubJson(settings, path, maxBytes, label)) {
|
|
6509
|
+
return {
|
|
6510
|
+
resolveCommit: async (owner, repository, ref) => {
|
|
6511
|
+
const payload = recordValue(
|
|
6512
|
+
await requestJson(
|
|
6513
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/commits/${encodeURIComponent(ref)}`,
|
|
6514
|
+
githubMetadataMaxBytes,
|
|
6515
|
+
"GitHub Skill commit"
|
|
6516
|
+
),
|
|
6517
|
+
"GitHub commit"
|
|
6518
|
+
);
|
|
6519
|
+
const sha = stringValue(payload.sha);
|
|
6520
|
+
if (!sha) throw new Error("GitHub commit response omitted sha");
|
|
6521
|
+
return sha.toLowerCase();
|
|
6522
|
+
},
|
|
6523
|
+
listTree: async (owner, repository, commit) => {
|
|
6524
|
+
const payload = recordValue(
|
|
6525
|
+
await requestJson(
|
|
6526
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/git/trees/${encodeURIComponent(commit)}?recursive=1`,
|
|
6527
|
+
githubMetadataMaxBytes,
|
|
6528
|
+
"GitHub Skill tree"
|
|
6529
|
+
),
|
|
6530
|
+
"GitHub tree"
|
|
6531
|
+
);
|
|
6532
|
+
if (payload.truncated === true) {
|
|
6533
|
+
throw new Error("GitHub repository tree is too large to import safely");
|
|
6534
|
+
}
|
|
6535
|
+
if (!Array.isArray(payload.tree)) throw new Error("GitHub tree response omitted entries");
|
|
6536
|
+
return payload.tree.map((entry, index) => {
|
|
6537
|
+
const record3 = recordValue(entry, `GitHub tree entry ${index}`);
|
|
6538
|
+
const path = stringValue(record3.path);
|
|
6539
|
+
const type = stringValue(record3.type);
|
|
6540
|
+
const mode = stringValue(record3.mode);
|
|
6541
|
+
const sha = stringValue(record3.sha);
|
|
6542
|
+
const size = record3.size;
|
|
6543
|
+
if (!path || type !== "blob" && type !== "tree" && type !== "commit" || !mode || !sha || size !== void 0 && size !== null && (typeof size !== "number" || !Number.isSafeInteger(size) || size < 0)) {
|
|
6544
|
+
throw new Error(`GitHub tree entry ${index} is invalid`);
|
|
6545
|
+
}
|
|
6546
|
+
return { path, type, mode, sha, size: typeof size === "number" ? size : null };
|
|
6547
|
+
});
|
|
6548
|
+
},
|
|
6549
|
+
readBlob: async (owner, repository, sha) => {
|
|
6550
|
+
const payload = recordValue(
|
|
6551
|
+
await requestJson(
|
|
6552
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/git/blobs/${encodeURIComponent(sha)}`,
|
|
6553
|
+
githubBlobResponseMaxBytes,
|
|
6554
|
+
"GitHub Skill file"
|
|
6555
|
+
),
|
|
6556
|
+
"GitHub blob"
|
|
6557
|
+
);
|
|
6558
|
+
if (payload.encoding !== "base64" || typeof payload.content !== "string") {
|
|
6559
|
+
throw new Error("GitHub Skill file did not use base64 encoding");
|
|
6560
|
+
}
|
|
6561
|
+
const normalized = payload.content.replace(/\s+/gu, "");
|
|
6562
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(normalized)) {
|
|
6563
|
+
throw new Error("GitHub Skill file contained invalid base64");
|
|
6564
|
+
}
|
|
6565
|
+
const bytes = Uint8Array.from(Buffer.from(normalized, "base64"));
|
|
6566
|
+
if (typeof payload.size === "number" && Number.isSafeInteger(payload.size) && payload.size !== bytes.byteLength) {
|
|
6567
|
+
throw new Error("GitHub Skill file size did not match its payload");
|
|
6568
|
+
}
|
|
6569
|
+
return bytes;
|
|
6570
|
+
}
|
|
6571
|
+
};
|
|
6572
|
+
}
|
|
6573
|
+
async function githubJson(settings, path, maxBytes, label) {
|
|
6574
|
+
const controller = new AbortController();
|
|
6575
|
+
const timeout = setTimeout(() => controller.abort(), githubRequestTimeoutMs);
|
|
6576
|
+
try {
|
|
6577
|
+
const response = await pinnedFetch2(
|
|
6578
|
+
`${githubApiBase}${path}`,
|
|
6579
|
+
{
|
|
6580
|
+
method: "GET",
|
|
6581
|
+
headers: {
|
|
6582
|
+
accept: "application/vnd.github+json",
|
|
6583
|
+
"user-agent": "OpenGeni-Capabilities",
|
|
6584
|
+
"x-github-api-version": "2022-11-28"
|
|
6585
|
+
},
|
|
6586
|
+
signal: controller.signal
|
|
6587
|
+
},
|
|
6588
|
+
settings,
|
|
6589
|
+
{ label, requireHttpsOutsideLocalTest: true }
|
|
6590
|
+
);
|
|
6591
|
+
if (!response.ok) {
|
|
6592
|
+
await readResponseTextBounded(response, 8192, `${label} error`).catch(() => void 0);
|
|
6593
|
+
if (response.status === 404) throw new Error(`${label} was not found or is not public`);
|
|
6594
|
+
if (response.status === 403 || response.status === 429) {
|
|
6595
|
+
throw new Error(`${label} is temporarily unavailable because GitHub limited the request`);
|
|
6596
|
+
}
|
|
6597
|
+
throw new Error(`${label} failed with HTTP ${response.status}`);
|
|
6598
|
+
}
|
|
6599
|
+
return await readResponseJsonBounded2(response, maxBytes, label, {
|
|
6600
|
+
signal: controller.signal
|
|
6601
|
+
});
|
|
6602
|
+
} finally {
|
|
6603
|
+
clearTimeout(timeout);
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
function recordValue(value, label) {
|
|
6607
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
6608
|
+
throw new Error(`${label} response is invalid`);
|
|
6609
|
+
}
|
|
6610
|
+
return value;
|
|
6611
|
+
}
|
|
6612
|
+
function stringValue(value) {
|
|
6613
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
6614
|
+
}
|
|
6615
|
+
|
|
6183
6616
|
// src/domain/environments.ts
|
|
6184
6617
|
import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes2 } from "@opengeni/config";
|
|
6185
6618
|
import {
|
|
@@ -8253,6 +8686,7 @@ import {
|
|
|
8253
8686
|
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS as DEFAULT_FIRST_PARTY_MCP_PERMISSIONS2,
|
|
8254
8687
|
OPENGENI_SLACK_BOT_SESSION_METADATA_KEY as OPENGENI_SLACK_BOT_SESSION_METADATA_KEY3,
|
|
8255
8688
|
resolveWorkspaceSessionToolDefaults as resolveWorkspaceSessionToolDefaults3,
|
|
8689
|
+
resolveBundledSkillSelection as resolveBundledSkillSelection2,
|
|
8256
8690
|
SessionAgentAccess,
|
|
8257
8691
|
SessionEndUser,
|
|
8258
8692
|
SessionMemoryScope
|
|
@@ -8320,6 +8754,7 @@ import {
|
|
|
8320
8754
|
FIRST_PARTY_MCP_TOOL_NAMES as FIRST_PARTY_MCP_TOOL_NAMES2,
|
|
8321
8755
|
OPENGENI_SLACK_BOT_SESSION_METADATA_KEY as OPENGENI_SLACK_BOT_SESSION_METADATA_KEY2,
|
|
8322
8756
|
SessionSkills,
|
|
8757
|
+
resolveBundledSkillSelection,
|
|
8323
8758
|
SessionSpawnDenial,
|
|
8324
8759
|
ServiceTurnInitiator,
|
|
8325
8760
|
ServiceTurnInitiatorContext,
|
|
@@ -9364,6 +9799,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
9364
9799
|
initialModelContext: input.modelContext ?? null,
|
|
9365
9800
|
resources: input.resources,
|
|
9366
9801
|
skills: input.skills ?? [],
|
|
9802
|
+
bundledSkillIds: input.bundledSkillIds,
|
|
9367
9803
|
tools: input.tools,
|
|
9368
9804
|
toolPolicy: input.toolPolicy,
|
|
9369
9805
|
metadata: sessionMetadata,
|
|
@@ -9448,6 +9884,7 @@ async function createAndStartSessionWithOutcome(input) {
|
|
|
9448
9884
|
initialModelContext: input.modelContext ?? null,
|
|
9449
9885
|
resources: input.resources,
|
|
9450
9886
|
skills: input.skills ?? [],
|
|
9887
|
+
bundledSkillIds: input.bundledSkillIds,
|
|
9451
9888
|
tools: input.tools,
|
|
9452
9889
|
toolPolicy: input.toolPolicy,
|
|
9453
9890
|
metadata: sessionMetadata,
|
|
@@ -10038,6 +10475,17 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10038
10475
|
message: error instanceof Error ? error.message : "invalid child visibility"
|
|
10039
10476
|
});
|
|
10040
10477
|
}
|
|
10478
|
+
let bundledSkillIds;
|
|
10479
|
+
try {
|
|
10480
|
+
bundledSkillIds = resolveBundledSkillSelection(
|
|
10481
|
+
payload.bundledSkillIds,
|
|
10482
|
+
parentSession?.bundledSkillIds
|
|
10483
|
+
);
|
|
10484
|
+
} catch (error) {
|
|
10485
|
+
throw new HTTPException12(422, {
|
|
10486
|
+
message: error instanceof Error ? error.message : "Invalid bundled Skill selection"
|
|
10487
|
+
});
|
|
10488
|
+
}
|
|
10041
10489
|
const sessionScope = resolveSessionCreateScope({
|
|
10042
10490
|
requested: {
|
|
10043
10491
|
agentAccess: payload.agentAccess,
|
|
@@ -10070,6 +10518,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10070
10518
|
if (payload.idempotencyKey && (effectiveVisibility !== "user_private" || replayManagedHumanSubjectId !== null)) {
|
|
10071
10519
|
try {
|
|
10072
10520
|
const initializedReplay = await getInitializedSessionCreateReplay(db, {
|
|
10521
|
+
bundledSkillIds,
|
|
10073
10522
|
accountId: grant.accountId,
|
|
10074
10523
|
workspaceId,
|
|
10075
10524
|
subjectId: replayManagedHumanSubjectId ?? grant.subjectId,
|
|
@@ -10619,6 +11068,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
|
|
|
10619
11068
|
modelContext: payload.modelContext ?? null,
|
|
10620
11069
|
resources,
|
|
10621
11070
|
skills,
|
|
11071
|
+
bundledSkillIds,
|
|
10622
11072
|
tools,
|
|
10623
11073
|
toolPolicy,
|
|
10624
11074
|
...payload.clientEventId ? { clientEventId: payload.clientEventId } : {},
|
|
@@ -11661,6 +12111,11 @@ async function validateScheduledTaskTarget(input) {
|
|
|
11661
12111
|
if (!session || session.accountId !== input.grant.accountId) {
|
|
11662
12112
|
throw new HTTPException13(404, { message: "target session not found" });
|
|
11663
12113
|
}
|
|
12114
|
+
if (input.agentConfig.bundledSkillIds !== void 0 && !isDeepStrictEqual(input.agentConfig.bundledSkillIds, session.bundledSkillIds)) {
|
|
12115
|
+
throw new HTTPException13(422, {
|
|
12116
|
+
message: "An existing-session schedule cannot change that session's bundled Skill selection"
|
|
12117
|
+
});
|
|
12118
|
+
}
|
|
11664
12119
|
if (session.status === "cancelled") {
|
|
11665
12120
|
throw new HTTPException13(409, {
|
|
11666
12121
|
message: "target session is cancelled; choose a revivable session"
|
|
@@ -12201,6 +12656,24 @@ function manualScheduledTaskTriggerUsageKey(workspaceId, taskId, triggerToken) {
|
|
|
12201
12656
|
return `agent_run.created:scheduled-trigger:${workspaceId}:${taskId}:${triggerToken}`;
|
|
12202
12657
|
}
|
|
12203
12658
|
async function validateScheduledTaskAgentConfig(input) {
|
|
12659
|
+
const actor = creationInitiatorForGrant(input.grant).actor;
|
|
12660
|
+
const parent = actor ? await getSession3(input.db, input.workspaceId, actor.sessionId) : null;
|
|
12661
|
+
if (actor && (!parent || parent.accountId !== input.grant.accountId)) {
|
|
12662
|
+
throw new HTTPException13(403, {
|
|
12663
|
+
message: "Scheduled Skill selection requires the creating agent's session"
|
|
12664
|
+
});
|
|
12665
|
+
}
|
|
12666
|
+
let bundledSkillIds;
|
|
12667
|
+
try {
|
|
12668
|
+
bundledSkillIds = resolveBundledSkillSelection2(
|
|
12669
|
+
input.payload.agentConfig.bundledSkillIds,
|
|
12670
|
+
parent?.bundledSkillIds
|
|
12671
|
+
);
|
|
12672
|
+
} catch (error) {
|
|
12673
|
+
throw new HTTPException13(422, {
|
|
12674
|
+
message: error instanceof Error ? error.message : "Invalid bundled Skill selection"
|
|
12675
|
+
});
|
|
12676
|
+
}
|
|
12204
12677
|
const model = canonicalConfiguredModel(input.settings, input.payload.agentConfig.model);
|
|
12205
12678
|
await assertWorkspaceModelPolicyAllows(input.db, input.settings, input.workspaceId, model);
|
|
12206
12679
|
const resources = normalizeResources(input.payload.agentConfig.resources ?? []);
|
|
@@ -12260,6 +12733,7 @@ async function validateScheduledTaskAgentConfig(input) {
|
|
|
12260
12733
|
}
|
|
12261
12734
|
const validated = {
|
|
12262
12735
|
...input.payload.agentConfig,
|
|
12736
|
+
...bundledSkillIds !== void 0 ? { bundledSkillIds } : {},
|
|
12263
12737
|
...model === void 0 || model === null ? {} : { model },
|
|
12264
12738
|
prompt,
|
|
12265
12739
|
resources,
|
|
@@ -13954,6 +14428,12 @@ function createRememberRouter(options) {
|
|
|
13954
14428
|
async remember(input) {
|
|
13955
14429
|
const attempt = await attemptOf(input.attempt);
|
|
13956
14430
|
const request = RememberRequest.parse(input.request);
|
|
14431
|
+
if (request.lane === "preference") {
|
|
14432
|
+
throw new RememberError(
|
|
14433
|
+
"preference_retired",
|
|
14434
|
+
"The Remember preference lane is retired; use skill_save for shared Skill files. No note or proposal was created."
|
|
14435
|
+
);
|
|
14436
|
+
}
|
|
13957
14437
|
const note = await createNote(options.db, {
|
|
13958
14438
|
...attempt,
|
|
13959
14439
|
operationId: derivedRememberOperationId(request.operationId, "note"),
|
|
@@ -16620,6 +17100,7 @@ export {
|
|
|
16620
17100
|
PR_REVIEW_AUTOMATION_ADAPTER_ID,
|
|
16621
17101
|
PR_REVIEW_AUTOMATION_TEMPLATE_ID,
|
|
16622
17102
|
ProductionEditableArtifactSnapshotVerifier,
|
|
17103
|
+
PublicSkillSearchError,
|
|
16623
17104
|
RIG_DEFAULT_VARIABLE_SET_LOAD_CONCURRENCY,
|
|
16624
17105
|
RememberError,
|
|
16625
17106
|
SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
|
|
@@ -16657,6 +17138,7 @@ export {
|
|
|
16657
17138
|
appendRigSetupCommand,
|
|
16658
17139
|
applyCapabilityEnablement,
|
|
16659
17140
|
applyGitHubRepositoryBindings,
|
|
17141
|
+
approveSkill,
|
|
16660
17142
|
assertAllowedEnvironmentVariableName,
|
|
16661
17143
|
assertAllowedVariableSetVariableName,
|
|
16662
17144
|
assertBoundedArtifactTitle,
|
|
@@ -16721,7 +17203,9 @@ export {
|
|
|
16721
17203
|
createCompanyBrainLearningPolicyRouter,
|
|
16722
17204
|
createCompanyProfileAgentAdminRouter,
|
|
16723
17205
|
createCompanyProfileDurableLearningAdapter,
|
|
17206
|
+
createGitHubSkillSourceClient,
|
|
16724
17207
|
createGovernedLearningActivationController,
|
|
17208
|
+
createPublicSkillSearchClient,
|
|
16725
17209
|
createRememberRouter,
|
|
16726
17210
|
createRigForApi,
|
|
16727
17211
|
createRigVersionForApi,
|
|
@@ -16814,6 +17298,7 @@ export {
|
|
|
16814
17298
|
inlinePackSkillInstall,
|
|
16815
17299
|
insightsSessionLabel,
|
|
16816
17300
|
inspectEditableArtifactLiveWireEnvelope,
|
|
17301
|
+
installSkill,
|
|
16817
17302
|
isAcceptedMimeType,
|
|
16818
17303
|
isAuthoritativeGitHubRepositorySelectionError,
|
|
16819
17304
|
isBuiltInCapabilityPack,
|
|
@@ -16834,6 +17319,7 @@ export {
|
|
|
16834
17319
|
listManagedHumanUserResourceAuthorities,
|
|
16835
17320
|
listRigChangesForApi,
|
|
16836
17321
|
listRigVersionsForApi,
|
|
17322
|
+
listSkills,
|
|
16837
17323
|
listWorkspaceCapabilityPacks,
|
|
16838
17324
|
loadRigDefaultVariableSetEnvironment,
|
|
16839
17325
|
lockActiveCustomModelForAdmission,
|
|
@@ -16900,6 +17386,7 @@ export {
|
|
|
16900
17386
|
provisionSandbox,
|
|
16901
17387
|
publishGovernedLearningEventToSlack,
|
|
16902
17388
|
readSessionLineage,
|
|
17389
|
+
readSkill,
|
|
16903
17390
|
recordRigAuditEvent,
|
|
16904
17391
|
recordVariableSetAuditEvent,
|
|
16905
17392
|
recordWorkspaceUsage,
|
|
@@ -16908,6 +17395,7 @@ export {
|
|
|
16908
17395
|
relayDialBaseFromSettings,
|
|
16909
17396
|
releaseManagedAuthRequestActorLease,
|
|
16910
17397
|
rememberConfirmationLabel,
|
|
17398
|
+
replayPortableSkillInstall,
|
|
16911
17399
|
reportSessionUsageRecordingFailure,
|
|
16912
17400
|
requireAccessContext,
|
|
16913
17401
|
requireAccessGrant,
|
|
@@ -16953,6 +17441,7 @@ export {
|
|
|
16953
17441
|
resolveWorkspaceLegacyRuntimePacks,
|
|
16954
17442
|
resolveWorkspaceModelSelection,
|
|
16955
17443
|
restoreScheduledTask,
|
|
17444
|
+
restoreSkill,
|
|
16956
17445
|
retainedProcessBackgroundSettlement,
|
|
16957
17446
|
revokeManagedHumanUserResourceGrant,
|
|
16958
17447
|
rigActorForGrant,
|
|
@@ -16968,6 +17457,7 @@ export {
|
|
|
16968
17457
|
sanitizeSlackPublicationText,
|
|
16969
17458
|
saveActorNewSessionDraft,
|
|
16970
17459
|
saveHumanComposerDraft,
|
|
17460
|
+
saveSkill,
|
|
16971
17461
|
saveWorkspaceMemoryWithSlackPublication,
|
|
16972
17462
|
scheduledConnectionSurfaceEligibility,
|
|
16973
17463
|
scheduledSlackBotConnectionId,
|
|
@@ -16992,6 +17482,7 @@ export {
|
|
|
16992
17482
|
settingsWithRigProviderImage,
|
|
16993
17483
|
settingsWithSessionMcpServerMetadata,
|
|
16994
17484
|
signedJsonAutomationAdapter,
|
|
17485
|
+
skillBundleHash,
|
|
16995
17486
|
stableJson3 as stableJson,
|
|
16996
17487
|
statusForVoiceInputError,
|
|
16997
17488
|
steerAgentSession,
|
|
@@ -17021,6 +17512,7 @@ export {
|
|
|
17021
17512
|
validateOpenGeniSlackBotConnectionSelection,
|
|
17022
17513
|
validateScheduledTaskMachineTarget,
|
|
17023
17514
|
validateScheduledTaskTarget,
|
|
17515
|
+
validateSkillFiles,
|
|
17024
17516
|
validateToolRefs,
|
|
17025
17517
|
validateToolRefsForSessionPolicy,
|
|
17026
17518
|
validateVariableSetAttachment,
|