@ai-sdk/harness 1.0.101 → 1.0.102
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/CHANGELOG.md +19 -0
- package/dist/agent/index.d.ts +65 -4
- package/dist/agent/index.js +493 -441
- package/dist/agent/index.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/utils/index.d.ts +17 -3
- package/dist/utils/index.js +321 -18
- package/dist/utils/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agent/harness-agent-session.ts +5 -0
- package/src/agent/harness-agent-settings.ts +82 -3
- package/src/agent/harness-agent.ts +67 -0
- package/src/agent/internal/harness-stream-text-result.ts +33 -13
- package/src/agent/internal/run-prompt.ts +209 -87
- package/src/agent/internal/turn-telemetry.ts +293 -444
- package/src/utils/index.ts +5 -0
- package/src/utils/write-instructions.ts +373 -0
- package/src/utils/write-skills.ts +59 -2
- package/src/v1/harness-v1-session.ts +5 -0
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
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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 };
|
package/dist/utils/index.js
CHANGED
|
@@ -573,18 +573,285 @@ function shellQuote(value) {
|
|
|
573
573
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
-
// src/utils/write-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
863
|
-
await
|
|
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 =
|
|
1145
|
+
const skillDir = path3.posix.join(rootDir, skill.name);
|
|
878
1146
|
for (const file of skill.files) {
|
|
879
1147
|
await sandbox.writeTextFile({
|
|
880
|
-
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
|
|
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(/^\/+/, "") :
|
|
935
|
-
const invalid = normalized === "" || mode === "relative" && normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || normalized.endsWith("/..") || mode === "relative" &&
|
|
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
|
|
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
|
|
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
|
|
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
|