@ai-sdk/harness 1.0.101 → 1.0.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -916,6 +916,10 @@ declare function getHarnessV1BuiltinToolFilteringDenialReason(input: {
916
916
  * calling the adapter, so adapters never need to derive provider-specific paths.
917
917
  */
918
918
  type HarnessV1StartOptions = {
919
+ /**
920
+ * Additional normalized HTTP headers to send with model requests.
921
+ */
922
+ readonly headers?: Readonly<Record<string, string>>;
919
923
  /**
920
924
  * Stable identifier for this harness session. Used as the underlying
921
925
  * resource name where the adapter has a notion of a named session
@@ -522,10 +522,24 @@ declare function resolveSandboxHomeDir({ sandbox, abortSignal, }: {
522
522
 
523
523
  declare function shellQuote(value: string): string;
524
524
 
525
+ type WriteInstructionsOptions = {
526
+ sandbox: Experimental_SandboxSession;
527
+ homePath: string;
528
+ instructionsFile: string;
529
+ instructions?: string;
530
+ abortSignal?: AbortSignal;
531
+ };
532
+ type WriteInstructionsResult = {
533
+ changed: boolean;
534
+ filePath: string;
535
+ };
536
+ declare function writeInstructions({ sandbox, homePath, instructionsFile, instructions, abortSignal, }: WriteInstructionsOptions): Promise<WriteInstructionsResult>;
537
+
525
538
  type SkillFilePathMode = 'relative' | 'strip-leading-slashes';
526
539
  type WriteSkillsOptions = {
527
540
  sandbox: Experimental_SandboxSession;
528
- rootDir: string;
541
+ homePath: string;
542
+ skillsDir: string;
529
543
  skills: ReadonlyArray<HarnessV1Skill>;
530
544
  abortSignal?: AbortSignal;
531
545
  skillNamePattern?: RegExp;
@@ -545,7 +559,7 @@ type WriteSkillsResult = {
545
559
  removed: string[];
546
560
  unchanged: string[];
547
561
  };
548
- declare function writeSkills({ sandbox, rootDir, skills, abortSignal, skillNamePattern, invalidSkillNameMessage, filePathMode, invalidSkillFilePathMessage, trailingNewline, }: WriteSkillsOptions): Promise<WriteSkillsResult>;
562
+ declare function writeSkills({ sandbox, homePath, skillsDir, skills, abortSignal, skillNamePattern, invalidSkillNameMessage, filePathMode, invalidSkillFilePathMessage, trailingNewline, }: WriteSkillsOptions): Promise<WriteSkillsResult>;
549
563
 
550
564
  type BridgeReadySource = 'stdout' | 'metadata';
551
565
  type BridgeReadyErrorContext = {
@@ -625,4 +639,4 @@ declare function resolveSandboxDefaultWorkingDirectory({ sandboxSession, abortSi
625
639
 
626
640
  declare function getRestrictedSandboxSession(sandboxSession: HarnessV1NetworkSandboxSession | Experimental_SandboxSession): Experimental_SandboxSession;
627
641
 
628
- export { type BridgeReadyErrorContext, type BridgeReadySource, type DiskLogRecoveryMode, type Experimental_BridgeUserMessageRequest, type Experimental_BridgeUserMessageResponse, type Experimental_BridgeUserMessageSubmitter, SandboxChannel, type SandboxChannelDebugEvent, type SandboxChannelOptions, type SandboxChannelReconnectOptions, type SkillFilePathMode, type WaitForBridgeReadyOptions, type WaitForBridgeReadyResult, type WriteSkillsOptions, type WriteSkillsResult, applyCredentialForwarding, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, createBridgeToken, createCredentialRequestTransformation, createReadBridgeAsset, createSandboxCredentialEnvironment, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, formatBridgeError, forwardBridgeProcessStream, generateSandboxCredentialPlaceholder, getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isHarnessAuthenticationEnvironment, isSandboxCredentialPlaceholder, logBridgeError, markBridgeStarting, maskSandboxCredentials, resolveSandboxDefaultWorkingDirectory, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, warnCredentialBrokeringUnavailable, withBridgeToken, writeSkills };
642
+ export { type BridgeReadyErrorContext, type BridgeReadySource, type DiskLogRecoveryMode, type Experimental_BridgeUserMessageRequest, type Experimental_BridgeUserMessageResponse, type Experimental_BridgeUserMessageSubmitter, SandboxChannel, type SandboxChannelDebugEvent, type SandboxChannelOptions, type SandboxChannelReconnectOptions, type SkillFilePathMode, type WaitForBridgeReadyOptions, type WaitForBridgeReadyResult, type WriteInstructionsOptions, type WriteInstructionsResult, type WriteSkillsOptions, type WriteSkillsResult, applyCredentialForwarding, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, createBridgeToken, createCredentialRequestTransformation, createReadBridgeAsset, createSandboxCredentialEnvironment, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, formatBridgeError, forwardBridgeProcessStream, generateSandboxCredentialPlaceholder, getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isHarnessAuthenticationEnvironment, isSandboxCredentialPlaceholder, logBridgeError, markBridgeStarting, maskSandboxCredentials, resolveSandboxDefaultWorkingDirectory, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, warnCredentialBrokeringUnavailable, withBridgeToken, writeInstructions, writeSkills };
@@ -573,18 +573,285 @@ function shellQuote(value) {
573
573
  return `'${value.replace(/'/g, `'\\''`)}'`;
574
574
  }
575
575
 
576
- // src/utils/write-skills.ts
577
- import { createHash } from "crypto";
576
+ // src/utils/write-instructions.ts
578
577
  import path2 from "path";
579
578
  import {
580
579
  safeParseJSON as safeParseJSON3
581
580
  } from "@ai-sdk/provider-utils";
581
+ var INSTRUCTIONS_METADATA_VERSION = 1;
582
+ async function writeInstructions({
583
+ sandbox,
584
+ homePath,
585
+ instructionsFile,
586
+ instructions,
587
+ abortSignal
588
+ }) {
589
+ const { targetPath, metadataPath } = resolveInstructionsFilePath({
590
+ homePath,
591
+ instructionsFile
592
+ });
593
+ const hasInstructions = typeof instructions === "string" && instructions.trim().length > 0;
594
+ const trimmedInstructions = hasInstructions ? instructions.trim() : "";
595
+ const currentDiskContent = await sandbox.readTextFile({
596
+ path: targetPath,
597
+ abortSignal
598
+ });
599
+ const existingMetadata = await readInstructionsMetadata({
600
+ sandbox,
601
+ metadataPath,
602
+ abortSignal
603
+ });
604
+ if (hasInstructions) {
605
+ const originalContent = deriveOriginalContent({
606
+ currentDiskContent,
607
+ existingMetadata
608
+ });
609
+ const targetContent = originalContent != null && originalContent.trim().length > 0 ? `${originalContent.replace(/\n+$/, "")}
610
+
611
+ ${trimmedInstructions}
612
+ ` : `${trimmedInstructions}
613
+ `;
614
+ if (currentDiskContent === targetContent && existingMetadata != null && existingMetadata.instructions === trimmedInstructions && existingMetadata.originalContent === originalContent) {
615
+ return { changed: false, filePath: targetPath };
616
+ }
617
+ await sandbox.writeTextFile({
618
+ path: targetPath,
619
+ content: targetContent,
620
+ abortSignal
621
+ });
622
+ await writeInstructionsMetadata({
623
+ sandbox,
624
+ metadataPath,
625
+ metadata: {
626
+ version: INSTRUCTIONS_METADATA_VERSION,
627
+ originalContent,
628
+ instructions: trimmedInstructions,
629
+ appliedContent: targetContent
630
+ },
631
+ abortSignal
632
+ });
633
+ return { changed: true, filePath: targetPath };
634
+ }
635
+ if (existingMetadata == null) {
636
+ return { changed: false, filePath: targetPath };
637
+ }
638
+ const restoredContent = deriveRestoredContent({
639
+ currentDiskContent,
640
+ existingMetadata
641
+ });
642
+ if (restoredContent != null) {
643
+ const contentToWrite = `${restoredContent.replace(/\n+$/, "")}
644
+ `;
645
+ await sandbox.writeTextFile({
646
+ path: targetPath,
647
+ content: contentToWrite,
648
+ abortSignal
649
+ });
650
+ } else {
651
+ await removeTargetFile({
652
+ sandbox,
653
+ targetPath,
654
+ abortSignal
655
+ });
656
+ }
657
+ await removeMetadataFile({
658
+ sandbox,
659
+ metadataPath,
660
+ abortSignal
661
+ });
662
+ return { changed: true, filePath: targetPath };
663
+ }
664
+ function deriveOriginalContent({
665
+ currentDiskContent,
666
+ existingMetadata
667
+ }) {
668
+ var _a;
669
+ if (existingMetadata == null) {
670
+ return currentDiskContent;
671
+ }
672
+ if (currentDiskContent == null) {
673
+ return existingMetadata.originalContent;
674
+ }
675
+ if (currentDiskContent === existingMetadata.appliedContent) {
676
+ return existingMetadata.originalContent;
677
+ }
678
+ const trimmedDisk = currentDiskContent.replace(/\n+$/, "");
679
+ const expectedSuffix = `
680
+
681
+ ${existingMetadata.instructions}`;
682
+ if (trimmedDisk.endsWith(expectedSuffix)) {
683
+ const userBase = trimmedDisk.slice(0, -expectedSuffix.length);
684
+ return userBase.length > 0 ? userBase : null;
685
+ }
686
+ if (trimmedDisk === existingMetadata.instructions) {
687
+ return null;
688
+ }
689
+ return (_a = existingMetadata.originalContent) != null ? _a : currentDiskContent;
690
+ }
691
+ function deriveRestoredContent({
692
+ currentDiskContent,
693
+ existingMetadata
694
+ }) {
695
+ if (currentDiskContent == null) {
696
+ return null;
697
+ }
698
+ if (currentDiskContent === existingMetadata.appliedContent) {
699
+ return existingMetadata.originalContent != null && existingMetadata.originalContent.trim().length > 0 ? existingMetadata.originalContent : null;
700
+ }
701
+ const trimmedDisk = currentDiskContent.replace(/\n+$/, "");
702
+ const expectedSuffix = `
703
+
704
+ ${existingMetadata.instructions}`;
705
+ if (trimmedDisk.endsWith(expectedSuffix)) {
706
+ const userBase = trimmedDisk.slice(0, -expectedSuffix.length);
707
+ return userBase.length > 0 ? userBase : null;
708
+ }
709
+ if (trimmedDisk === existingMetadata.instructions) {
710
+ return null;
711
+ }
712
+ return currentDiskContent;
713
+ }
714
+ function resolveInstructionsFilePath({
715
+ homePath,
716
+ instructionsFile
717
+ }) {
718
+ if (typeof homePath !== "string" || homePath.trim().length === 0) {
719
+ throw new Error("Invalid homePath: expected a non-empty string.");
720
+ }
721
+ if (!path2.posix.isAbsolute(homePath)) {
722
+ throw new Error(
723
+ `Invalid homePath ${JSON.stringify(homePath)}: expected an absolute POSIX path.`
724
+ );
725
+ }
726
+ if (typeof instructionsFile !== "string" || instructionsFile.trim().length === 0) {
727
+ throw new Error(
728
+ `Invalid instructionsFile ${JSON.stringify(instructionsFile)}: expected a relative POSIX path without traversal.`
729
+ );
730
+ }
731
+ const containsTraversal = instructionsFile.split(/[\\\/]/).some((segment) => segment === "..");
732
+ const normalizedInstructionsFile = path2.posix.normalize(
733
+ instructionsFile.trim()
734
+ );
735
+ const normalizedNoTrailingSlash = normalizedInstructionsFile.replace(
736
+ /\/+$/,
737
+ ""
738
+ );
739
+ if (instructionsFile.includes("\\") || path2.posix.isAbsolute(instructionsFile) || path2.win32.isAbsolute(instructionsFile) || containsTraversal || instructionsFile.endsWith("/") || instructionsFile.endsWith("\\") || instructionsFile.endsWith("/.") || instructionsFile.endsWith("/..") || normalizedNoTrailingSlash === "" || normalizedNoTrailingSlash === "." || normalizedInstructionsFile.startsWith("../") || normalizedInstructionsFile.includes("/../") || normalizedInstructionsFile.endsWith("/..")) {
740
+ throw new Error(
741
+ `Invalid instructionsFile ${JSON.stringify(instructionsFile)}: expected a relative POSIX path without traversal.`
742
+ );
743
+ }
744
+ const targetPath = path2.posix.join(homePath, normalizedNoTrailingSlash);
745
+ const relative = path2.posix.relative(homePath, targetPath);
746
+ if (relative === "" || relative.startsWith("..") || path2.posix.isAbsolute(relative)) {
747
+ throw new Error(
748
+ `Invalid instructionsFile ${JSON.stringify(instructionsFile)}: must be a subpath within homePath ${JSON.stringify(homePath)}.`
749
+ );
750
+ }
751
+ const dir = path2.posix.dirname(targetPath);
752
+ const base = path2.posix.basename(targetPath);
753
+ const metadataPath = path2.posix.join(
754
+ dir,
755
+ `.${base}.ai-sdk-harness-instructions.json`
756
+ );
757
+ return { targetPath, metadataPath };
758
+ }
759
+ async function readInstructionsMetadata({
760
+ sandbox,
761
+ metadataPath,
762
+ abortSignal
763
+ }) {
764
+ const content = await sandbox.readTextFile({
765
+ path: metadataPath,
766
+ abortSignal
767
+ });
768
+ if (content == null) return void 0;
769
+ const parsed = await safeParseJSON3({ text: content });
770
+ if (!parsed.success || !isInstructionsMetadata(parsed.value)) {
771
+ throw new Error(
772
+ `Invalid AI SDK harness instructions metadata: ${metadataPath}`
773
+ );
774
+ }
775
+ return parsed.value;
776
+ }
777
+ function isInstructionsMetadata(value) {
778
+ if (value == null || typeof value !== "object" || Array.isArray(value)) {
779
+ return false;
780
+ }
781
+ const candidate = value;
782
+ return candidate.version === INSTRUCTIONS_METADATA_VERSION && (candidate.originalContent === null || typeof candidate.originalContent === "string") && typeof candidate.instructions === "string" && typeof candidate.appliedContent === "string";
783
+ }
784
+ async function writeInstructionsMetadata({
785
+ sandbox,
786
+ metadataPath,
787
+ metadata,
788
+ abortSignal
789
+ }) {
790
+ const temporaryPath = `${metadataPath}.tmp`;
791
+ await sandbox.writeTextFile({
792
+ path: temporaryPath,
793
+ content: `${JSON.stringify(metadata, null, 2)}
794
+ `,
795
+ abortSignal
796
+ });
797
+ await runSandboxCommand({
798
+ sandbox,
799
+ command: `mv -f ${shellQuote(temporaryPath)} ${shellQuote(metadataPath)}`,
800
+ abortSignal,
801
+ errorMessage: `Failed to update instructions metadata: ${metadataPath}`
802
+ });
803
+ }
804
+ async function removeMetadataFile({
805
+ sandbox,
806
+ metadataPath,
807
+ abortSignal
808
+ }) {
809
+ await runSandboxCommand({
810
+ sandbox,
811
+ command: `rm -f -- ${shellQuote(metadataPath)}`,
812
+ abortSignal,
813
+ errorMessage: `Failed to remove instructions metadata: ${metadataPath}`
814
+ });
815
+ }
816
+ async function removeTargetFile({
817
+ sandbox,
818
+ targetPath,
819
+ abortSignal
820
+ }) {
821
+ await runSandboxCommand({
822
+ sandbox,
823
+ command: `rm -f -- ${shellQuote(targetPath)}`,
824
+ abortSignal,
825
+ errorMessage: `Failed to remove instructions file: ${targetPath}`
826
+ });
827
+ }
828
+ async function runSandboxCommand({
829
+ sandbox,
830
+ command,
831
+ abortSignal,
832
+ errorMessage
833
+ }) {
834
+ const result = await sandbox.run({ command, abortSignal });
835
+ if (result.exitCode !== 0) {
836
+ throw new Error(
837
+ `${errorMessage} (exit ${result.exitCode})${result.stderr ? `: ${result.stderr}` : ""}`
838
+ );
839
+ }
840
+ }
841
+
842
+ // src/utils/write-skills.ts
843
+ import { createHash } from "crypto";
844
+ import path3 from "path";
845
+ import {
846
+ safeParseJSON as safeParseJSON4
847
+ } from "@ai-sdk/provider-utils";
582
848
  var SKILLS_MANIFEST_FILENAME = ".ai-sdk-harness-skills.json";
583
849
  var SKILLS_MANIFEST_VERSION = 1;
584
850
  var SAFE_MANIFEST_SKILL_NAME = /^[A-Za-z0-9._-]+$/;
585
851
  async function writeSkills({
586
852
  sandbox,
587
- rootDir,
853
+ homePath,
854
+ skillsDir,
588
855
  skills,
589
856
  abortSignal,
590
857
  skillNamePattern = /^[A-Za-z0-9._-]+$/,
@@ -594,6 +861,7 @@ async function writeSkills({
594
861
  trailingNewline = false
595
862
  }) {
596
863
  var _a;
864
+ const rootDir = resolveSkillsRootDir({ homePath, skillsDir });
597
865
  const projectedSkills = skills.map(
598
866
  (skill) => projectSkill({
599
867
  skill,
@@ -605,7 +873,7 @@ async function writeSkills({
605
873
  })
606
874
  ).sort((a, b) => a.name.localeCompare(b.name));
607
875
  assertUniqueSkillNames(projectedSkills);
608
- const manifestPath = path2.posix.join(rootDir, SKILLS_MANIFEST_FILENAME);
876
+ const manifestPath = path3.posix.join(rootDir, SKILLS_MANIFEST_FILENAME);
609
877
  const existingManifest = await readSkillsManifest({
610
878
  sandbox,
611
879
  manifestPath,
@@ -729,7 +997,7 @@ async function ensureSkillsDirectory({
729
997
  rootDir,
730
998
  abortSignal
731
999
  }) {
732
- await runSandboxCommand({
1000
+ await runSandboxCommand2({
733
1001
  sandbox,
734
1002
  command: `mkdir -p ${shellQuote(rootDir)}`,
735
1003
  abortSignal,
@@ -788,7 +1056,7 @@ async function readSkillsManifest({
788
1056
  abortSignal
789
1057
  });
790
1058
  if (content == null) return void 0;
791
- const parsed = await safeParseJSON3({ text: content });
1059
+ const parsed = await safeParseJSON4({ text: content });
792
1060
  if (!parsed.success || !isSkillsManifest(parsed.value)) {
793
1061
  throw new Error(`Invalid AI SDK harness skills manifest: ${manifestPath}`);
794
1062
  }
@@ -828,7 +1096,7 @@ async function writeSkillsManifest({
828
1096
  `,
829
1097
  abortSignal
830
1098
  });
831
- await runSandboxCommand({
1099
+ await runSandboxCommand2({
832
1100
  sandbox,
833
1101
  command: `mv -f ${shellQuote(temporaryPath)} ${shellQuote(manifestPath)}`,
834
1102
  abortSignal,
@@ -841,7 +1109,7 @@ async function assertSkillDirectoryAvailable({
841
1109
  skillName,
842
1110
  abortSignal
843
1111
  }) {
844
- const skillDir = path2.posix.join(rootDir, skillName);
1112
+ const skillDir = path3.posix.join(rootDir, skillName);
845
1113
  const result = await sandbox.run({
846
1114
  command: `test ! -e ${shellQuote(skillDir)}`,
847
1115
  abortSignal
@@ -859,8 +1127,8 @@ async function removeSkillDirectories({
859
1127
  abortSignal
860
1128
  }) {
861
1129
  if (skillNames.length === 0) return;
862
- const directories = Array.from(new Set(skillNames)).sort((a, b) => a.localeCompare(b)).map((name) => shellQuote(path2.posix.join(rootDir, name))).join(" ");
863
- await runSandboxCommand({
1130
+ const directories = Array.from(new Set(skillNames)).sort((a, b) => a.localeCompare(b)).map((name) => shellQuote(path3.posix.join(rootDir, name))).join(" ");
1131
+ await runSandboxCommand2({
864
1132
  sandbox,
865
1133
  command: `rm -rf -- ${directories}`,
866
1134
  abortSignal,
@@ -874,17 +1142,17 @@ async function writeProjectedSkills({
874
1142
  abortSignal
875
1143
  }) {
876
1144
  for (const skill of skills) {
877
- const skillDir = path2.posix.join(rootDir, skill.name);
1145
+ const skillDir = path3.posix.join(rootDir, skill.name);
878
1146
  for (const file of skill.files) {
879
1147
  await sandbox.writeTextFile({
880
- path: path2.posix.join(skillDir, file.path),
1148
+ path: path3.posix.join(skillDir, file.path),
881
1149
  content: file.content,
882
1150
  abortSignal
883
1151
  });
884
1152
  }
885
1153
  }
886
1154
  }
887
- async function runSandboxCommand({
1155
+ async function runSandboxCommand2({
888
1156
  sandbox,
889
1157
  command,
890
1158
  abortSignal,
@@ -931,8 +1199,8 @@ function normalizeSkillFilePath({
931
1199
  message
932
1200
  }) {
933
1201
  var _a;
934
- const normalized = mode === "strip-leading-slashes" ? filePath.replace(/^\/+/, "") : path2.posix.normalize(filePath);
935
- const invalid = normalized === "" || mode === "relative" && normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..") || mode === "relative" && path2.posix.isAbsolute(normalized);
1202
+ const normalized = mode === "strip-leading-slashes" ? filePath.replace(/^\/+/, "") : path3.posix.normalize(filePath);
1203
+ const invalid = normalized === "" || mode === "relative" && normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..") || mode === "relative" && path3.posix.isAbsolute(normalized);
936
1204
  if (invalid) {
937
1205
  throw new Error(
938
1206
  (_a = message == null ? void 0 : message({ skillName: skillName != null ? skillName : "", filePath })) != null ? _a : `Invalid skill file path for ${skillName}: ${filePath}`
@@ -953,10 +1221,44 @@ ${skill.content}`;
953
1221
  return trailingNewline ? `${content}
954
1222
  ` : content;
955
1223
  }
1224
+ function resolveSkillsRootDir({
1225
+ homePath,
1226
+ skillsDir
1227
+ }) {
1228
+ if (typeof homePath !== "string" || homePath.trim().length === 0) {
1229
+ throw new Error("Invalid homePath: expected a non-empty string.");
1230
+ }
1231
+ if (!path3.posix.isAbsolute(homePath)) {
1232
+ throw new Error(
1233
+ `Invalid homePath ${JSON.stringify(homePath)}: expected an absolute POSIX path.`
1234
+ );
1235
+ }
1236
+ if (typeof skillsDir !== "string" || skillsDir.trim().length === 0) {
1237
+ throw new Error(
1238
+ `Invalid skillsDir ${JSON.stringify(skillsDir)}: expected a relative POSIX path without traversal.`
1239
+ );
1240
+ }
1241
+ const containsTraversal = skillsDir.split(/[\\\/]/).some((segment) => segment === "..");
1242
+ const normalizedSkillsDir = path3.posix.normalize(skillsDir.trim());
1243
+ const normalizedNoTrailingSlash = normalizedSkillsDir.replace(/\/+$/, "");
1244
+ if (skillsDir.includes("\\") || path3.posix.isAbsolute(skillsDir) || path3.win32.isAbsolute(skillsDir) || containsTraversal || normalizedNoTrailingSlash === "" || normalizedNoTrailingSlash === "." || normalizedSkillsDir.startsWith("../") || normalizedSkillsDir.includes("/../") || normalizedSkillsDir.endsWith("/..")) {
1245
+ throw new Error(
1246
+ `Invalid skillsDir ${JSON.stringify(skillsDir)}: expected a relative POSIX path without traversal.`
1247
+ );
1248
+ }
1249
+ const rootDir = path3.posix.join(homePath, normalizedNoTrailingSlash);
1250
+ const relative = path3.posix.relative(homePath, rootDir);
1251
+ if (relative === "" || relative.startsWith("..") || path3.posix.isAbsolute(relative)) {
1252
+ throw new Error(
1253
+ `Invalid skillsDir ${JSON.stringify(skillsDir)}: must be a subpath within homePath ${JSON.stringify(homePath)}.`
1254
+ );
1255
+ }
1256
+ return rootDir;
1257
+ }
956
1258
 
957
1259
  // src/utils/bridge-ready.ts
958
1260
  import {
959
- safeParseJSON as safeParseJSON4
1261
+ safeParseJSON as safeParseJSON5
960
1262
  } from "@ai-sdk/provider-utils";
961
1263
  import { z as z4 } from "zod/v4";
962
1264
 
@@ -1448,7 +1750,7 @@ async function waitForBridgeReady({
1448
1750
  if (value === void 0) continue;
1449
1751
  for (const line of decoder.push(value)) {
1450
1752
  pushTail({ lines: stdoutTail, line });
1451
- const parsed = await safeParseJSON4({
1753
+ const parsed = await safeParseJSON5({
1452
1754
  text: line,
1453
1755
  schema: harnessV1BridgeReadySchema
1454
1756
  });
@@ -1485,7 +1787,7 @@ async function readBridgeMetaReady({
1485
1787
  })
1486
1788
  ).catch(() => null);
1487
1789
  if (raw == null) return void 0;
1488
- const parsed = await safeParseJSON4({ text: raw, schema: bridgeMetaSchema });
1790
+ const parsed = await safeParseJSON5({ text: raw, schema: bridgeMetaSchema });
1489
1791
  if (!parsed.success) return void 0;
1490
1792
  if (parsed.value.type !== bridgeType) return void 0;
1491
1793
  if (parsed.value.state !== "waiting") return void 0;
@@ -1782,6 +2084,7 @@ export {
1782
2084
  waitForBridgeReady,
1783
2085
  warnCredentialBrokeringUnavailable,
1784
2086
  withBridgeToken,
2087
+ writeInstructions,
1785
2088
  writeSkills
1786
2089
  };
1787
2090
  //# sourceMappingURL=index.js.map