@ai-sdk/harness 1.0.91 → 1.0.93
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 +22 -0
- package/README.md +5 -1
- package/dist/agent/index.d.ts +155 -110
- package/dist/agent/index.js +292 -99
- package/dist/agent/index.js.map +1 -1
- package/dist/bridge/index.d.ts +20 -1
- package/dist/bridge/index.js +29 -5
- package/dist/bridge/index.js.map +1 -1
- package/dist/index.d.ts +115 -94
- package/dist/utils/index.d.ts +18 -2
- package/dist/utils/index.js +327 -33
- package/dist/utils/index.js.map +1 -1
- package/package.json +4 -4
- package/src/agent/harness-agent-session.ts +107 -7
- package/src/agent/harness-agent-settings.ts +86 -7
- package/src/agent/harness-agent.ts +322 -112
- package/src/agent/internal/lifecycle-state-validation.ts +3 -0
- package/src/agent/internal/run-prompt.ts +16 -6
- package/src/bridge/index.ts +73 -6
- package/src/utils/authentication-environment.ts +20 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/write-skills.ts +419 -32
- package/src/v1/harness-authentication.ts +24 -0
- package/src/v1/harness-v1-lifecycle-state.ts +38 -0
- package/src/v1/harness-v1-session.ts +9 -40
- package/src/v1/index.ts +5 -0
package/dist/utils/index.d.ts
CHANGED
|
@@ -233,6 +233,16 @@ declare function getAiGatewayAuthFromEnv({ env, }: {
|
|
|
233
233
|
baseUrl: string;
|
|
234
234
|
};
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Authentication-related environment variables supplied directly to a harness.
|
|
238
|
+
*
|
|
239
|
+
* When used as an adapter's `auth` option, this environment replaces the host
|
|
240
|
+
* process environment for authentication discovery.
|
|
241
|
+
*/
|
|
242
|
+
type HarnessV1AuthenticationEnvironment = Readonly<Record<string, string>>;
|
|
243
|
+
|
|
244
|
+
declare function isHarnessAuthenticationEnvironment(value: unknown): value is HarnessV1AuthenticationEnvironment;
|
|
245
|
+
|
|
236
246
|
/**
|
|
237
247
|
* Connection details for a sandbox-exposed port. Headers are scoped to the
|
|
238
248
|
* returned URL and must be included when opening the connection.
|
|
@@ -521,7 +531,13 @@ type WriteSkillsOptions = {
|
|
|
521
531
|
}) => string;
|
|
522
532
|
trailingNewline?: boolean;
|
|
523
533
|
};
|
|
524
|
-
|
|
534
|
+
type WriteSkillsResult = {
|
|
535
|
+
changed: boolean;
|
|
536
|
+
written: string[];
|
|
537
|
+
removed: string[];
|
|
538
|
+
unchanged: string[];
|
|
539
|
+
};
|
|
540
|
+
declare function writeSkills({ sandbox, rootDir, skills, abortSignal, skillNamePattern, invalidSkillNameMessage, filePathMode, invalidSkillFilePathMessage, trailingNewline, }: WriteSkillsOptions): Promise<WriteSkillsResult>;
|
|
525
541
|
|
|
526
542
|
type BridgeReadySource = 'stdout' | 'metadata';
|
|
527
543
|
type BridgeReadyErrorContext = {
|
|
@@ -591,4 +607,4 @@ declare function resolveSandboxDefaultWorkingDirectory({ sandboxSession, abortSi
|
|
|
591
607
|
|
|
592
608
|
declare function getRestrictedSandboxSession(sandboxSession: HarnessV1NetworkSandboxSession | Experimental_SandboxSession): Experimental_SandboxSession;
|
|
593
609
|
|
|
594
|
-
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, applyCredentialForwarding, classifyDiskLog, createBridgeErrorHandler, createBridgeStartupError, createCredentialRequestTransformation, createSandboxCredentialEnvironment, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, formatBridgeError, forwardBridgeProcessStream, generateSandboxCredentialPlaceholder, getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isSandboxCredentialPlaceholder, logBridgeError, markBridgeStarting, maskSandboxCredentials, resolveSandboxDefaultWorkingDirectory, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, warnCredentialBrokeringUnavailable, writeSkills };
|
|
610
|
+
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, createCredentialRequestTransformation, createSandboxCredentialEnvironment, drainBridgeProcessStream, experimental_createBridgeUserMessageSubmitter, formatBridgeError, forwardBridgeProcessStream, generateSandboxCredentialPlaceholder, getAiGatewayAuthFromEnv, getRestrictedSandboxSession, isHarnessAuthenticationEnvironment, isSandboxCredentialPlaceholder, logBridgeError, markBridgeStarting, maskSandboxCredentials, resolveSandboxDefaultWorkingDirectory, resolveSandboxHomeDir, shellQuote, waitForBridgeReady, warnCredentialBrokeringUnavailable, writeSkills };
|
package/dist/utils/index.js
CHANGED
|
@@ -437,6 +437,19 @@ function getAiGatewayAuthFromEnv({
|
|
|
437
437
|
};
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
// src/utils/authentication-environment.ts
|
|
441
|
+
function isHarnessAuthenticationEnvironment(value) {
|
|
442
|
+
if (value == null || typeof value !== "object") {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
if (Array.isArray(value) || Object.values(value).some((entry) => typeof entry !== "string")) {
|
|
446
|
+
throw new TypeError(
|
|
447
|
+
"Invalid auth: expected an authentication mode or a flat record with string values."
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
440
453
|
// src/utils/sandbox-credential-brokering.ts
|
|
441
454
|
import { randomBytes } from "crypto";
|
|
442
455
|
var SANDBOX_CREDENTIAL_PLACEHOLDER_PREFIX = "aisdkhc_";
|
|
@@ -547,7 +560,14 @@ function shellQuote(value) {
|
|
|
547
560
|
}
|
|
548
561
|
|
|
549
562
|
// src/utils/write-skills.ts
|
|
563
|
+
import { createHash } from "crypto";
|
|
550
564
|
import path2 from "path";
|
|
565
|
+
import {
|
|
566
|
+
safeParseJSON as safeParseJSON3
|
|
567
|
+
} from "@ai-sdk/provider-utils";
|
|
568
|
+
var SKILLS_MANIFEST_FILENAME = ".ai-sdk-harness-skills.json";
|
|
569
|
+
var SKILLS_MANIFEST_VERSION = 1;
|
|
570
|
+
var SAFE_MANIFEST_SKILL_NAME = /^[A-Za-z0-9._-]+$/;
|
|
551
571
|
async function writeSkills({
|
|
552
572
|
sandbox,
|
|
553
573
|
rootDir,
|
|
@@ -559,60 +579,333 @@ async function writeSkills({
|
|
|
559
579
|
invalidSkillFilePathMessage = ({ skillName, filePath }) => `Invalid skill file path for ${skillName}: ${filePath}`,
|
|
560
580
|
trailingNewline = false
|
|
561
581
|
}) {
|
|
562
|
-
var _a
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
582
|
+
var _a;
|
|
583
|
+
const projectedSkills = skills.map(
|
|
584
|
+
(skill) => projectSkill({
|
|
585
|
+
skill,
|
|
586
|
+
skillNamePattern,
|
|
587
|
+
invalidSkillNameMessage,
|
|
588
|
+
filePathMode,
|
|
589
|
+
invalidSkillFilePathMessage,
|
|
590
|
+
trailingNewline
|
|
591
|
+
})
|
|
592
|
+
).sort((a, b) => a.name.localeCompare(b.name));
|
|
593
|
+
assertUniqueSkillNames(projectedSkills);
|
|
594
|
+
const manifestPath = path2.posix.join(rootDir, SKILLS_MANIFEST_FILENAME);
|
|
595
|
+
const existingManifest = await readSkillsManifest({
|
|
596
|
+
sandbox,
|
|
597
|
+
manifestPath,
|
|
598
|
+
abortSignal
|
|
599
|
+
});
|
|
600
|
+
const nextEntries = projectedSkills.map(({ name, hash }) => ({ name, hash }));
|
|
601
|
+
const nextByName = new Map(nextEntries.map((entry) => [entry.name, entry]));
|
|
602
|
+
if ((existingManifest == null ? void 0 : existingManifest.state) === "pending") {
|
|
603
|
+
await ensureSkillsDirectory({ sandbox, rootDir, abortSignal });
|
|
604
|
+
const recoveryNames = existingManifest.skills.map((skill) => skill.name);
|
|
605
|
+
await removeSkillDirectories({
|
|
606
|
+
sandbox,
|
|
607
|
+
rootDir,
|
|
608
|
+
skillNames: recoveryNames,
|
|
609
|
+
abortSignal
|
|
568
610
|
});
|
|
569
|
-
|
|
611
|
+
const removed2 = recoveryNames.filter((name) => !nextByName.has(name)).sort((a, b) => a.localeCompare(b));
|
|
612
|
+
await writeProjectedSkills({
|
|
613
|
+
sandbox,
|
|
614
|
+
rootDir,
|
|
615
|
+
skills: projectedSkills,
|
|
616
|
+
abortSignal
|
|
617
|
+
});
|
|
618
|
+
await writeSkillsManifest({
|
|
619
|
+
sandbox,
|
|
620
|
+
manifestPath,
|
|
621
|
+
manifest: {
|
|
622
|
+
version: SKILLS_MANIFEST_VERSION,
|
|
623
|
+
state: "complete",
|
|
624
|
+
skills: nextEntries
|
|
625
|
+
},
|
|
626
|
+
abortSignal
|
|
627
|
+
});
|
|
628
|
+
return {
|
|
629
|
+
changed: recoveryNames.length > 0 || projectedSkills.length > 0,
|
|
630
|
+
written: projectedSkills.map((skill) => skill.name),
|
|
631
|
+
removed: removed2,
|
|
632
|
+
unchanged: []
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
const previousEntries = (_a = existingManifest == null ? void 0 : existingManifest.skills) != null ? _a : [];
|
|
636
|
+
const previousByName = new Map(
|
|
637
|
+
previousEntries.map((entry) => [entry.name, entry])
|
|
638
|
+
);
|
|
639
|
+
const removed = previousEntries.filter((entry) => !nextByName.has(entry.name)).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
640
|
+
const written = projectedSkills.filter((skill) => {
|
|
641
|
+
var _a2;
|
|
642
|
+
return ((_a2 = previousByName.get(skill.name)) == null ? void 0 : _a2.hash) !== skill.hash;
|
|
643
|
+
}).map((skill) => skill.name).sort((a, b) => a.localeCompare(b));
|
|
644
|
+
const unchanged = projectedSkills.filter((skill) => {
|
|
645
|
+
var _a2;
|
|
646
|
+
return ((_a2 = previousByName.get(skill.name)) == null ? void 0 : _a2.hash) === skill.hash;
|
|
647
|
+
}).map((skill) => skill.name).sort((a, b) => a.localeCompare(b));
|
|
648
|
+
const changed = removed.length > 0 || written.length > 0;
|
|
649
|
+
if (!changed && existingManifest != null) {
|
|
650
|
+
return { changed: false, written, removed, unchanged };
|
|
651
|
+
}
|
|
652
|
+
const previouslyOwned = new Set(previousEntries.map((entry) => entry.name));
|
|
653
|
+
for (const skillName of written) {
|
|
654
|
+
if (previouslyOwned.has(skillName)) continue;
|
|
655
|
+
await assertSkillDirectoryAvailable({
|
|
656
|
+
sandbox,
|
|
657
|
+
rootDir,
|
|
658
|
+
skillName,
|
|
659
|
+
abortSignal
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
await ensureSkillsDirectory({ sandbox, rootDir, abortSignal });
|
|
663
|
+
const pendingNames = Array.from(
|
|
664
|
+
/* @__PURE__ */ new Set([
|
|
665
|
+
...previousEntries.map((entry) => entry.name),
|
|
666
|
+
...nextEntries.map((entry) => entry.name)
|
|
667
|
+
])
|
|
668
|
+
).sort((a, b) => a.localeCompare(b));
|
|
669
|
+
await writeSkillsManifest({
|
|
670
|
+
sandbox,
|
|
671
|
+
manifestPath,
|
|
672
|
+
manifest: {
|
|
673
|
+
version: SKILLS_MANIFEST_VERSION,
|
|
674
|
+
state: "pending",
|
|
675
|
+
skills: pendingNames.map((name) => {
|
|
676
|
+
var _a2, _b;
|
|
677
|
+
return {
|
|
678
|
+
name,
|
|
679
|
+
hash: (_b = (_a2 = nextByName.get(name)) == null ? void 0 : _a2.hash) != null ? _b : previousByName.get(name).hash
|
|
680
|
+
};
|
|
681
|
+
})
|
|
682
|
+
},
|
|
683
|
+
abortSignal
|
|
684
|
+
});
|
|
685
|
+
await removeSkillDirectories({
|
|
686
|
+
sandbox,
|
|
687
|
+
rootDir,
|
|
688
|
+
skillNames: [
|
|
689
|
+
...removed,
|
|
690
|
+
...written.filter((name) => previouslyOwned.has(name))
|
|
691
|
+
],
|
|
692
|
+
abortSignal
|
|
693
|
+
});
|
|
694
|
+
const writtenSet = new Set(written);
|
|
695
|
+
await writeProjectedSkills({
|
|
696
|
+
sandbox,
|
|
697
|
+
rootDir,
|
|
698
|
+
skills: projectedSkills.filter((skill) => writtenSet.has(skill.name)),
|
|
699
|
+
abortSignal
|
|
700
|
+
});
|
|
701
|
+
await writeSkillsManifest({
|
|
702
|
+
sandbox,
|
|
703
|
+
manifestPath,
|
|
704
|
+
manifest: {
|
|
705
|
+
version: SKILLS_MANIFEST_VERSION,
|
|
706
|
+
state: "complete",
|
|
707
|
+
skills: nextEntries
|
|
708
|
+
},
|
|
709
|
+
abortSignal
|
|
710
|
+
});
|
|
711
|
+
return { changed, written, removed, unchanged };
|
|
712
|
+
}
|
|
713
|
+
async function ensureSkillsDirectory({
|
|
714
|
+
sandbox,
|
|
715
|
+
rootDir,
|
|
716
|
+
abortSignal
|
|
717
|
+
}) {
|
|
718
|
+
await runSandboxCommand({
|
|
719
|
+
sandbox,
|
|
720
|
+
command: `mkdir -p ${shellQuote(rootDir)}`,
|
|
721
|
+
abortSignal,
|
|
722
|
+
errorMessage: `Failed to create skills directory: ${rootDir}`
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
function projectSkill({
|
|
726
|
+
skill,
|
|
727
|
+
skillNamePattern,
|
|
728
|
+
invalidSkillNameMessage,
|
|
729
|
+
filePathMode,
|
|
730
|
+
invalidSkillFilePathMessage,
|
|
731
|
+
trailingNewline
|
|
732
|
+
}) {
|
|
733
|
+
var _a;
|
|
734
|
+
const name = validateSkillName({
|
|
735
|
+
name: skill.name,
|
|
736
|
+
pattern: skillNamePattern,
|
|
737
|
+
message: invalidSkillNameMessage
|
|
738
|
+
});
|
|
739
|
+
const files = /* @__PURE__ */ new Map();
|
|
740
|
+
files.set("SKILL.md", renderSkillFile({ skill, trailingNewline }));
|
|
741
|
+
for (const file of (_a = skill.files) != null ? _a : []) {
|
|
742
|
+
files.set(
|
|
570
743
|
normalizeSkillFilePath({
|
|
571
744
|
skillName: skill.name,
|
|
572
745
|
filePath: file.path,
|
|
573
746
|
mode: filePathMode,
|
|
574
747
|
message: invalidSkillFilePathMessage
|
|
575
|
-
})
|
|
748
|
+
}),
|
|
749
|
+
file.content
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const projectedFiles = Array.from(files, ([filePath, content]) => ({
|
|
753
|
+
path: filePath,
|
|
754
|
+
content
|
|
755
|
+
})).sort((a, b) => a.path.localeCompare(b.path));
|
|
756
|
+
const hash = createHash("sha256");
|
|
757
|
+
for (const file of projectedFiles) {
|
|
758
|
+
hash.update(String(Buffer.byteLength(file.path)));
|
|
759
|
+
hash.update(":");
|
|
760
|
+
hash.update(file.path);
|
|
761
|
+
hash.update(String(Buffer.byteLength(file.content)));
|
|
762
|
+
hash.update(":");
|
|
763
|
+
hash.update(file.content);
|
|
764
|
+
}
|
|
765
|
+
return { name, hash: hash.digest("hex"), files: projectedFiles };
|
|
766
|
+
}
|
|
767
|
+
async function readSkillsManifest({
|
|
768
|
+
sandbox,
|
|
769
|
+
manifestPath,
|
|
770
|
+
abortSignal
|
|
771
|
+
}) {
|
|
772
|
+
const content = await sandbox.readTextFile({
|
|
773
|
+
path: manifestPath,
|
|
774
|
+
abortSignal
|
|
775
|
+
});
|
|
776
|
+
if (content == null) return void 0;
|
|
777
|
+
const parsed = await safeParseJSON3({ text: content });
|
|
778
|
+
if (!parsed.success || !isSkillsManifest(parsed.value)) {
|
|
779
|
+
throw new Error(`Invalid AI SDK harness skills manifest: ${manifestPath}`);
|
|
780
|
+
}
|
|
781
|
+
return parsed.value;
|
|
782
|
+
}
|
|
783
|
+
function isSkillsManifest(value) {
|
|
784
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
const manifest = value;
|
|
788
|
+
if (manifest.version !== SKILLS_MANIFEST_VERSION || manifest.state !== "complete" && manifest.state !== "pending" || !Array.isArray(manifest.skills)) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
const names = /* @__PURE__ */ new Set();
|
|
792
|
+
for (const entry of manifest.skills) {
|
|
793
|
+
if (entry == null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
794
|
+
return false;
|
|
576
795
|
}
|
|
796
|
+
const skill = entry;
|
|
797
|
+
if (typeof skill.name !== "string" || !isSafeManifestSkillName(skill.name) || typeof skill.hash !== "string" || !/^[a-f0-9]{64}$/.test(skill.hash) || names.has(skill.name)) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
names.add(skill.name);
|
|
577
801
|
}
|
|
578
|
-
|
|
579
|
-
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
async function writeSkillsManifest({
|
|
805
|
+
sandbox,
|
|
806
|
+
manifestPath,
|
|
807
|
+
manifest,
|
|
808
|
+
abortSignal
|
|
809
|
+
}) {
|
|
810
|
+
const temporaryPath = `${manifestPath}.tmp`;
|
|
811
|
+
await sandbox.writeTextFile({
|
|
812
|
+
path: temporaryPath,
|
|
813
|
+
content: `${JSON.stringify(manifest, null, 2)}
|
|
814
|
+
`,
|
|
580
815
|
abortSignal
|
|
581
816
|
});
|
|
817
|
+
await runSandboxCommand({
|
|
818
|
+
sandbox,
|
|
819
|
+
command: `mv -f ${shellQuote(temporaryPath)} ${shellQuote(manifestPath)}`,
|
|
820
|
+
abortSignal,
|
|
821
|
+
errorMessage: `Failed to update skills manifest: ${manifestPath}`
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
async function assertSkillDirectoryAvailable({
|
|
825
|
+
sandbox,
|
|
826
|
+
rootDir,
|
|
827
|
+
skillName,
|
|
828
|
+
abortSignal
|
|
829
|
+
}) {
|
|
830
|
+
const skillDir = path2.posix.join(rootDir, skillName);
|
|
831
|
+
const result = await sandbox.run({
|
|
832
|
+
command: `test ! -e ${shellQuote(skillDir)}`,
|
|
833
|
+
abortSignal
|
|
834
|
+
});
|
|
835
|
+
if (result.exitCode !== 0) {
|
|
836
|
+
throw new Error(
|
|
837
|
+
`Cannot write harness skill '${skillName}': ${skillDir} already exists and is not owned by the AI SDK harness.`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function removeSkillDirectories({
|
|
842
|
+
sandbox,
|
|
843
|
+
rootDir,
|
|
844
|
+
skillNames,
|
|
845
|
+
abortSignal
|
|
846
|
+
}) {
|
|
847
|
+
if (skillNames.length === 0) return;
|
|
848
|
+
const directories = Array.from(new Set(skillNames)).sort((a, b) => a.localeCompare(b)).map((name) => shellQuote(path2.posix.join(rootDir, name))).join(" ");
|
|
849
|
+
await runSandboxCommand({
|
|
850
|
+
sandbox,
|
|
851
|
+
command: `rm -rf -- ${directories}`,
|
|
852
|
+
abortSignal,
|
|
853
|
+
errorMessage: `Failed to replace harness skills in: ${rootDir}`
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
async function writeProjectedSkills({
|
|
857
|
+
sandbox,
|
|
858
|
+
rootDir,
|
|
859
|
+
skills,
|
|
860
|
+
abortSignal
|
|
861
|
+
}) {
|
|
582
862
|
for (const skill of skills) {
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
pattern: skillNamePattern,
|
|
586
|
-
message: invalidSkillNameMessage
|
|
587
|
-
});
|
|
588
|
-
const skillDir = path2.posix.join(rootDir, name);
|
|
589
|
-
await sandbox.writeTextFile({
|
|
590
|
-
path: path2.posix.join(skillDir, "SKILL.md"),
|
|
591
|
-
content: renderSkillFile({ skill, trailingNewline }),
|
|
592
|
-
abortSignal
|
|
593
|
-
});
|
|
594
|
-
for (const file of (_b = skill.files) != null ? _b : []) {
|
|
595
|
-
const filePath = normalizeSkillFilePath({
|
|
596
|
-
skillName: skill.name,
|
|
597
|
-
filePath: file.path,
|
|
598
|
-
mode: filePathMode,
|
|
599
|
-
message: invalidSkillFilePathMessage
|
|
600
|
-
});
|
|
863
|
+
const skillDir = path2.posix.join(rootDir, skill.name);
|
|
864
|
+
for (const file of skill.files) {
|
|
601
865
|
await sandbox.writeTextFile({
|
|
602
|
-
path: path2.posix.join(skillDir,
|
|
866
|
+
path: path2.posix.join(skillDir, file.path),
|
|
603
867
|
content: file.content,
|
|
604
868
|
abortSignal
|
|
605
869
|
});
|
|
606
870
|
}
|
|
607
871
|
}
|
|
608
872
|
}
|
|
873
|
+
async function runSandboxCommand({
|
|
874
|
+
sandbox,
|
|
875
|
+
command,
|
|
876
|
+
abortSignal,
|
|
877
|
+
errorMessage
|
|
878
|
+
}) {
|
|
879
|
+
const result = await sandbox.run({ command, abortSignal });
|
|
880
|
+
if (result.exitCode !== 0) {
|
|
881
|
+
throw new Error(
|
|
882
|
+
`${errorMessage} (exit ${result.exitCode})${result.stderr ? `: ${result.stderr}` : ""}`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function assertUniqueSkillNames(skills) {
|
|
887
|
+
for (let index = 1; index < skills.length; index++) {
|
|
888
|
+
if (skills[index - 1].name === skills[index].name) {
|
|
889
|
+
throw new Error(`Duplicate skill name: ${skills[index].name}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function isSafeManifestSkillName(name) {
|
|
894
|
+
SAFE_MANIFEST_SKILL_NAME.lastIndex = 0;
|
|
895
|
+
const matches = SAFE_MANIFEST_SKILL_NAME.test(name);
|
|
896
|
+
SAFE_MANIFEST_SKILL_NAME.lastIndex = 0;
|
|
897
|
+
return matches && name !== "." && name !== ".." && !name.includes("/");
|
|
898
|
+
}
|
|
609
899
|
function validateSkillName({
|
|
610
900
|
name,
|
|
611
901
|
pattern,
|
|
612
902
|
message
|
|
613
903
|
}) {
|
|
614
904
|
var _a;
|
|
615
|
-
|
|
905
|
+
pattern.lastIndex = 0;
|
|
906
|
+
const matches = pattern.test(name);
|
|
907
|
+
pattern.lastIndex = 0;
|
|
908
|
+
if (!matches || name === "." || name === "..") {
|
|
616
909
|
throw new Error((_a = message == null ? void 0 : message({ name })) != null ? _a : `Invalid skill name: ${name}`);
|
|
617
910
|
}
|
|
618
911
|
return name;
|
|
@@ -649,7 +942,7 @@ ${skill.content}`;
|
|
|
649
942
|
|
|
650
943
|
// src/utils/bridge-ready.ts
|
|
651
944
|
import {
|
|
652
|
-
safeParseJSON as
|
|
945
|
+
safeParseJSON as safeParseJSON4
|
|
653
946
|
} from "@ai-sdk/provider-utils";
|
|
654
947
|
import { z as z4 } from "zod/v4";
|
|
655
948
|
|
|
@@ -1114,7 +1407,7 @@ async function waitForBridgeReady({
|
|
|
1114
1407
|
if (value === void 0) continue;
|
|
1115
1408
|
for (const line of decoder.push(value)) {
|
|
1116
1409
|
pushTail({ lines: stdoutTail, line });
|
|
1117
|
-
const parsed = await
|
|
1410
|
+
const parsed = await safeParseJSON4({
|
|
1118
1411
|
text: line,
|
|
1119
1412
|
schema: harnessV1BridgeReadySchema
|
|
1120
1413
|
});
|
|
@@ -1151,7 +1444,7 @@ async function readBridgeMetaReady({
|
|
|
1151
1444
|
})
|
|
1152
1445
|
).catch(() => null);
|
|
1153
1446
|
if (raw == null) return void 0;
|
|
1154
|
-
const parsed = await
|
|
1447
|
+
const parsed = await safeParseJSON4({ text: raw, schema: bridgeMetaSchema });
|
|
1155
1448
|
if (!parsed.success) return void 0;
|
|
1156
1449
|
if (parsed.value.type !== bridgeType) return void 0;
|
|
1157
1450
|
if (parsed.value.state !== "waiting") return void 0;
|
|
@@ -1413,6 +1706,7 @@ export {
|
|
|
1413
1706
|
generateSandboxCredentialPlaceholder,
|
|
1414
1707
|
getAiGatewayAuthFromEnv,
|
|
1415
1708
|
getRestrictedSandboxSession,
|
|
1709
|
+
isHarnessAuthenticationEnvironment,
|
|
1416
1710
|
isSandboxCredentialPlaceholder,
|
|
1417
1711
|
logBridgeError,
|
|
1418
1712
|
markBridgeStarting,
|