@opengeni/core 2.8.0-canary.0 → 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/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 retainWorkspaceMutationProcess(db, {
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
- CapabilityPack
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
- const parsed = CapabilityPack.safeParse(registration?.pack);
1161
- if (parsed.success) packs2.push(parsed.data);
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
- CapabilityPack as CapabilityPack2,
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)).flatMap((registration) => {
4201
- const parsed = CapabilityPack2.safeParse(registration.pack);
4202
- return parsed.success ? [parsed.data] : [];
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 = CapabilityPack2.safeParse(registration.pack);
4216
- return parsed.success ? parsed.data : null;
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 = buildPortableSkillArtifact(skill.files);
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 endpoint = safeEndpointLabel(endpointUrl);
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 ${endpoint}. Check the endpoint URL or choose a different catalog entry.`;
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 ${endpoint}. Check the endpoint configuration or try again.`;
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 buildPortableSkillArtifact2,
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 sourcePath = selectSkillRoot(parsed, tree);
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 = buildPortableSkillArtifact2(files);
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
- const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
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 requestedPath = normalizeGitHubPath(
6089
- mode === "blob" && pathSegments.at(-1)?.toLowerCase() === "skill.md" ? pathSegments.slice(0, -1) : pathSegments
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) || ".").sort();
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
- const candidates = source.skillSlug ? skillFiles.filter((path) => path.split("/").at(-1) === source.skillSlug) : skillFiles;
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
- output[index] = await map(values[index]);
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,
@@ -8790,6 +9225,7 @@ async function forkManagedHumanSession(deps, authorization, workspaceId, sourceS
8790
9225
  const forkInput = {
8791
9226
  sourceWorkspaceId: workspaceId,
8792
9227
  sourceSessionId,
9228
+ ...request.sourceEventId ? { sourceEventId: request.sourceEventId } : {},
8793
9229
  actorSubjectId: authorization.grant.subjectId,
8794
9230
  destinationWorkspaceId: workspaceId,
8795
9231
  destinationVisibility: request.visibility === "private" ? "user_private" : "workspace_shared",
@@ -9363,6 +9799,7 @@ async function createAndStartSessionWithOutcome(input) {
9363
9799
  initialModelContext: input.modelContext ?? null,
9364
9800
  resources: input.resources,
9365
9801
  skills: input.skills ?? [],
9802
+ bundledSkillIds: input.bundledSkillIds,
9366
9803
  tools: input.tools,
9367
9804
  toolPolicy: input.toolPolicy,
9368
9805
  metadata: sessionMetadata,
@@ -9447,6 +9884,7 @@ async function createAndStartSessionWithOutcome(input) {
9447
9884
  initialModelContext: input.modelContext ?? null,
9448
9885
  resources: input.resources,
9449
9886
  skills: input.skills ?? [],
9887
+ bundledSkillIds: input.bundledSkillIds,
9450
9888
  tools: input.tools,
9451
9889
  toolPolicy: input.toolPolicy,
9452
9890
  metadata: sessionMetadata,
@@ -10037,6 +10475,17 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
10037
10475
  message: error instanceof Error ? error.message : "invalid child visibility"
10038
10476
  });
10039
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
+ }
10040
10489
  const sessionScope = resolveSessionCreateScope({
10041
10490
  requested: {
10042
10491
  agentAccess: payload.agentAccess,
@@ -10069,6 +10518,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
10069
10518
  if (payload.idempotencyKey && (effectiveVisibility !== "user_private" || replayManagedHumanSubjectId !== null)) {
10070
10519
  try {
10071
10520
  const initializedReplay = await getInitializedSessionCreateReplay(db, {
10521
+ bundledSkillIds,
10072
10522
  accountId: grant.accountId,
10073
10523
  workspaceId,
10074
10524
  subjectId: replayManagedHumanSubjectId ?? grant.subjectId,
@@ -10618,6 +11068,7 @@ async function createSessionForRequestWithOutcome(unresolvedDeps, grant, workspa
10618
11068
  modelContext: payload.modelContext ?? null,
10619
11069
  resources,
10620
11070
  skills,
11071
+ bundledSkillIds,
10621
11072
  tools,
10622
11073
  toolPolicy,
10623
11074
  ...payload.clientEventId ? { clientEventId: payload.clientEventId } : {},
@@ -11660,6 +12111,11 @@ async function validateScheduledTaskTarget(input) {
11660
12111
  if (!session || session.accountId !== input.grant.accountId) {
11661
12112
  throw new HTTPException13(404, { message: "target session not found" });
11662
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
+ }
11663
12119
  if (session.status === "cancelled") {
11664
12120
  throw new HTTPException13(409, {
11665
12121
  message: "target session is cancelled; choose a revivable session"
@@ -12200,6 +12656,24 @@ function manualScheduledTaskTriggerUsageKey(workspaceId, taskId, triggerToken) {
12200
12656
  return `agent_run.created:scheduled-trigger:${workspaceId}:${taskId}:${triggerToken}`;
12201
12657
  }
12202
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
+ }
12203
12677
  const model = canonicalConfiguredModel(input.settings, input.payload.agentConfig.model);
12204
12678
  await assertWorkspaceModelPolicyAllows(input.db, input.settings, input.workspaceId, model);
12205
12679
  const resources = normalizeResources(input.payload.agentConfig.resources ?? []);
@@ -12259,6 +12733,7 @@ async function validateScheduledTaskAgentConfig(input) {
12259
12733
  }
12260
12734
  const validated = {
12261
12735
  ...input.payload.agentConfig,
12736
+ ...bundledSkillIds !== void 0 ? { bundledSkillIds } : {},
12262
12737
  ...model === void 0 || model === null ? {} : { model },
12263
12738
  prompt,
12264
12739
  resources,
@@ -13953,6 +14428,12 @@ function createRememberRouter(options) {
13953
14428
  async remember(input) {
13954
14429
  const attempt = await attemptOf(input.attempt);
13955
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
+ }
13956
14437
  const note = await createNote(options.db, {
13957
14438
  ...attempt,
13958
14439
  operationId: derivedRememberOperationId(request.operationId, "note"),
@@ -16619,6 +17100,7 @@ export {
16619
17100
  PR_REVIEW_AUTOMATION_ADAPTER_ID,
16620
17101
  PR_REVIEW_AUTOMATION_TEMPLATE_ID,
16621
17102
  ProductionEditableArtifactSnapshotVerifier,
17103
+ PublicSkillSearchError,
16622
17104
  RIG_DEFAULT_VARIABLE_SET_LOAD_CONCURRENCY,
16623
17105
  RememberError,
16624
17106
  SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
@@ -16656,6 +17138,7 @@ export {
16656
17138
  appendRigSetupCommand,
16657
17139
  applyCapabilityEnablement,
16658
17140
  applyGitHubRepositoryBindings,
17141
+ approveSkill,
16659
17142
  assertAllowedEnvironmentVariableName,
16660
17143
  assertAllowedVariableSetVariableName,
16661
17144
  assertBoundedArtifactTitle,
@@ -16720,7 +17203,9 @@ export {
16720
17203
  createCompanyBrainLearningPolicyRouter,
16721
17204
  createCompanyProfileAgentAdminRouter,
16722
17205
  createCompanyProfileDurableLearningAdapter,
17206
+ createGitHubSkillSourceClient,
16723
17207
  createGovernedLearningActivationController,
17208
+ createPublicSkillSearchClient,
16724
17209
  createRememberRouter,
16725
17210
  createRigForApi,
16726
17211
  createRigVersionForApi,
@@ -16813,6 +17298,7 @@ export {
16813
17298
  inlinePackSkillInstall,
16814
17299
  insightsSessionLabel,
16815
17300
  inspectEditableArtifactLiveWireEnvelope,
17301
+ installSkill,
16816
17302
  isAcceptedMimeType,
16817
17303
  isAuthoritativeGitHubRepositorySelectionError,
16818
17304
  isBuiltInCapabilityPack,
@@ -16833,6 +17319,7 @@ export {
16833
17319
  listManagedHumanUserResourceAuthorities,
16834
17320
  listRigChangesForApi,
16835
17321
  listRigVersionsForApi,
17322
+ listSkills,
16836
17323
  listWorkspaceCapabilityPacks,
16837
17324
  loadRigDefaultVariableSetEnvironment,
16838
17325
  lockActiveCustomModelForAdmission,
@@ -16899,6 +17386,7 @@ export {
16899
17386
  provisionSandbox,
16900
17387
  publishGovernedLearningEventToSlack,
16901
17388
  readSessionLineage,
17389
+ readSkill,
16902
17390
  recordRigAuditEvent,
16903
17391
  recordVariableSetAuditEvent,
16904
17392
  recordWorkspaceUsage,
@@ -16907,6 +17395,7 @@ export {
16907
17395
  relayDialBaseFromSettings,
16908
17396
  releaseManagedAuthRequestActorLease,
16909
17397
  rememberConfirmationLabel,
17398
+ replayPortableSkillInstall,
16910
17399
  reportSessionUsageRecordingFailure,
16911
17400
  requireAccessContext,
16912
17401
  requireAccessGrant,
@@ -16952,6 +17441,7 @@ export {
16952
17441
  resolveWorkspaceLegacyRuntimePacks,
16953
17442
  resolveWorkspaceModelSelection,
16954
17443
  restoreScheduledTask,
17444
+ restoreSkill,
16955
17445
  retainedProcessBackgroundSettlement,
16956
17446
  revokeManagedHumanUserResourceGrant,
16957
17447
  rigActorForGrant,
@@ -16967,6 +17457,7 @@ export {
16967
17457
  sanitizeSlackPublicationText,
16968
17458
  saveActorNewSessionDraft,
16969
17459
  saveHumanComposerDraft,
17460
+ saveSkill,
16970
17461
  saveWorkspaceMemoryWithSlackPublication,
16971
17462
  scheduledConnectionSurfaceEligibility,
16972
17463
  scheduledSlackBotConnectionId,
@@ -16991,6 +17482,7 @@ export {
16991
17482
  settingsWithRigProviderImage,
16992
17483
  settingsWithSessionMcpServerMetadata,
16993
17484
  signedJsonAutomationAdapter,
17485
+ skillBundleHash,
16994
17486
  stableJson3 as stableJson,
16995
17487
  statusForVoiceInputError,
16996
17488
  steerAgentSession,
@@ -17020,6 +17512,7 @@ export {
17020
17512
  validateOpenGeniSlackBotConnectionSelection,
17021
17513
  validateScheduledTaskMachineTarget,
17022
17514
  validateScheduledTaskTarget,
17515
+ validateSkillFiles,
17023
17516
  validateToolRefs,
17024
17517
  validateToolRefsForSessionPolicy,
17025
17518
  validateVariableSetAttachment,