2 Commits

Author SHA1 Message Date
Lyon 517d8d2b54 Merge pull request #395 from pikasTech/fix/394-resource-bundle-repository-map
修复 ResourceBundle 精确 Gitea 仓库映射消费
2026-07-22 07:34:02 +08:00
AgentRun Codex 56beca2a57 fix: 精确消费资源仓库映射 2026-07-22 01:29:58 +02:00
6 changed files with 233 additions and 44 deletions
+2 -1
View File
@@ -276,6 +276,7 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
const selectedSecret = context.secretRefs.find((item) => item.profile === options.run.backendProfile);
const codexHome = runnerCodexHome(options.run.backendProfile, selectedSecret, context.sessionPvc);
const bootRepoUrl = optionalString(options.bootRepoUrl) ?? defaultBootRepoUrl;
const resourceBundleRepositoriesJson = optionalString(process.env.AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON);
const env = dedupeEnvVars([
{ name: "AGENTRUN_MGR_URL", value: options.managerUrl },
{ name: "AGENTRUN_API_KEY", valueFrom: { secretKeyRef: context.runnerApiKeySecretRef } },
@@ -297,7 +298,7 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
{ name: "AGENTRUN_BOOT_MODE", value: "runner" },
{ name: "AGENTRUN_APP_ROOT", value: "/home/agentrun/agentrun-source" },
{ name: "AGENTRUN_RUNTIME_NAMESPACE", value: context.namespace },
...(optionalString(process.env.AGENTRUN_GIT_MIRROR_BASE_URL) ? [{ name: "AGENTRUN_GIT_MIRROR_BASE_URL", value: optionalString(process.env.AGENTRUN_GIT_MIRROR_BASE_URL) }] : []),
...(resourceBundleRepositoriesJson ? [{ name: "AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON", value: resourceBundleRepositoriesJson }] : []),
{ name: "AGENTRUN_K8S_JOB_NAME", value: context.jobName },
{ name: "AGENTRUN_LOG_PATH", value: "/tmp/agentrun-runner.jsonl" },
{ name: "AGENTRUN_WORK_READY_VERSION", value: String(staticWorkReadyCapabilitySummary().version) },
+122 -32
View File
@@ -58,15 +58,17 @@ interface GitCheckout {
branch: string | null;
}
interface GitMirrorConfig {
baseUrl: string;
interface ResourceBundleRepositoryMapping {
repository: string;
normalizedRepository: string;
readUrl: string;
}
interface GitBundleSource {
repoUrl: string;
commitId?: string;
ref?: string;
gitMirror?: GitMirrorConfig;
repositoryMappings?: readonly ResourceBundleRepositoryMapping[];
credentialRefPresent: boolean;
credentialEnv?: NodeJS.ProcessEnv;
}
@@ -145,7 +147,6 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
const assemblyContractHash = primaryAssemblyContractHash(resourceBundleRef, workspaceRef);
const bundleContractHash = stableHash(resourceBundleRef.bundles);
const initialState = await inspectPrimaryWorkspace(workspacePath, declaredSource, assemblyContractHash, bundleContractHash);
const gitMirror = gitMirrorConfig(resourceBundleRef, env);
let provenance: PrimaryWorkspaceProvenance;
let materializedBundles: MaterializedGitBundle[];
let checkoutPath: string | null = null;
@@ -159,7 +160,8 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
await rm(checkoutRoot, { recursive: true, force: true });
await mkdir(checkoutRoot, { recursive: true });
const credentialEnv = await resourceCredentialEnv(resourceBundleRef, env);
const defaultSource = defaultGitBundleSource(resourceBundleRef, env, gitMirror, credentialEnv);
const repositoryMappings = resourceBundleRepositoryMappings(resourceBundleRef, env);
const defaultSource = defaultGitBundleSource(resourceBundleRef, repositoryMappings, credentialEnv);
const checkoutCache = new Map<string, Promise<GitCheckout>>();
const checkoutFor = (source: GitBundleSource, sourceContext: GitBundleSourceContext) => {
const key = stableHash(gitSourceIdentity(source));
@@ -214,7 +216,7 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
fetchRepoUrl: resolvedSource.fetchRepoUrl,
mirrorUsed: resolvedSource.mirrorUsed,
mirrorBaseUrl: resolvedSource.mirrorBaseUrl,
gitMirror: resolvedSource.mirrorUsed ? { enabled: true, baseUrl: resolvedSource.mirrorBaseUrl, valuesPrinted: false } : { enabled: false, baseUrl: null, valuesPrinted: false },
repositoryAuthority: resolvedSource.mirrorUsed ? { repository: githubRepositoryIdentity(resourceBundleRef.repoUrl), mode: "exact-read-url", valuesPrinted: false } : null,
commitId: resolvedSource.commitId,
requestedCommitId: resourceBundleRef.commitId ?? null,
requestedRef: resourceBundleRef.ref ?? null,
@@ -254,33 +256,22 @@ function isSameOrChildPath(candidate: string, parent: string): boolean {
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
function gitMirrorConfig(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
void resourceBundleRef;
return defaultGitMirrorConfig(env);
}
function defaultGitMirrorConfig(env: NodeJS.ProcessEnv): GitMirrorConfig {
const baseUrl = optionalNonEmpty(env.AGENTRUN_GIT_MIRROR_BASE_URL) ?? "http://git-mirror-http.devops-infra.svc.cluster.local";
return { baseUrl: normalizeMirrorBaseUrl(baseUrl) };
}
function defaultGitBundleSource(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv, gitMirror?: GitMirrorConfig, credentialEnv?: NodeJS.ProcessEnv): GitBundleSource {
void env;
function defaultGitBundleSource(resourceBundleRef: ResourceBundleRef, repositoryMappings?: readonly ResourceBundleRepositoryMapping[], credentialEnv?: NodeJS.ProcessEnv): GitBundleSource {
const ref = optionalNonEmpty(resourceBundleRef.ref);
const commitId = optionalNonEmpty(resourceBundleRef.commitId);
const credentialRefPresent = resourceBundleRef.credentialRef !== undefined;
if (!ref && !commitId) throw new AgentRunError("schema-invalid", "gitbundle primary source must declare ref or commitId", { httpStatus: 400 });
return { repoUrl: resourceBundleRef.repoUrl, ...(ref ? { ref } : {}), ...(commitId ? { commitId } : {}), credentialRefPresent, ...(gitMirror ? { gitMirror } : {}), ...(credentialEnv ? { credentialEnv } : {}) };
return { repoUrl: resourceBundleRef.repoUrl, ...(ref ? { ref } : {}), ...(commitId ? { commitId } : {}), credentialRefPresent, ...(repositoryMappings ? { repositoryMappings } : {}), ...(credentialEnv ? { credentialEnv } : {}) };
}
function bundleGitSource(bundle: ResourceBundleRef["bundles"][number], resourceBundleRef: ResourceBundleRef, defaultSource: GitBundleSource): GitBundleSource {
const repoUrl = bundle.repoUrl ?? resourceBundleRef.repoUrl;
const ref = optionalNonEmpty(bundle.ref);
const mirror = defaultSource.gitMirror;
const repositoryMappings = defaultSource.repositoryMappings;
const credentialRefPresent = defaultSource.credentialRefPresent;
const credentialEnv = defaultSource.credentialEnv;
const commitId = optionalNonEmpty(bundle.commitId);
if (ref || commitId) return { repoUrl, ...(ref ? { ref } : {}), ...(commitId ? { commitId } : {}), credentialRefPresent, ...(mirror ? { gitMirror: mirror } : {}), ...(credentialEnv ? { credentialEnv } : {}) };
if (ref || commitId) return { repoUrl, ...(ref ? { ref } : {}), ...(commitId ? { commitId } : {}), credentialRefPresent, ...(repositoryMappings ? { repositoryMappings } : {}), ...(credentialEnv ? { credentialEnv } : {}) };
if (repoUrl === defaultSource.repoUrl) return defaultSource;
throw new AgentRunError("schema-invalid", "cross-repository bundle must declare its own ref or commitId", { httpStatus: 400 });
}
@@ -327,7 +318,12 @@ async function checkoutGitSource(checkoutRoot: string, source: GitBundleSource,
}
function gitSourceIdentity(source: GitBundleSource): JsonRecord {
return { repoUrl: source.repoUrl, commitId: source.commitId ?? null, ref: source.ref ?? null, gitMirror: source.gitMirror ? { baseUrl: source.gitMirror.baseUrl } : null };
const repositoryAuthority = source.repositoryMappings?.map((mapping) => ({
repository: mapping.repository,
normalizedRepository: mapping.normalizedRepository,
readUrl: mapping.readUrl,
})) ?? null;
return { repoUrl: source.repoUrl, commitId: source.commitId ?? null, ref: source.ref ?? null, repositoryAuthorityHash: repositoryAuthority ? stableHash(repositoryAuthority) : null };
}
async function resourceCredentialEnv(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): Promise<NodeJS.ProcessEnv | undefined> {
@@ -744,19 +740,113 @@ async function verifyPrimaryWorkspace(workspacePath: string, workspaceRef: Works
};
}
export function resolveGitBundleFetchSource(repoUrl: string, gitMirror?: GitMirrorConfig, env: NodeJS.ProcessEnv = process.env): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
const mirror = gitMirror ? { baseUrl: normalizeMirrorBaseUrl(gitMirror.baseUrl) } : defaultGitMirrorConfig(env);
const githubPath = githubRepoPath(repoUrl);
if (!githubPath) return { fetchRepoUrl: repoUrl, mirrorUsed: false };
return { fetchRepoUrl: `${mirror.baseUrl}/${githubPath}.git`, mirrorUsed: true, mirrorBaseUrl: mirror.baseUrl };
}
function normalizeMirrorBaseUrl(baseUrl: string): string {
return baseUrl.replace(/\/+$/u, "");
export function resolveGitBundleFetchSource(repoUrl: string, env: NodeJS.ProcessEnv = process.env): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
const repository = githubRepositoryIdentity(repoUrl);
if (!repository) return { fetchRepoUrl: repoUrl, mirrorUsed: false };
return resolveGitBundleFetchSourceFromMappings(repository, parseResourceBundleRepositoryMappings(env));
}
function gitFetchSource(source: GitBundleSource): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
return resolveGitBundleFetchSource(source.repoUrl, source.gitMirror);
const repository = githubRepositoryIdentity(source.repoUrl);
if (!repository) return { fetchRepoUrl: source.repoUrl, mirrorUsed: false };
return resolveGitBundleFetchSourceFromMappings(repository, source.repositoryMappings ?? []);
}
function resourceBundleRepositoryMappings(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): readonly ResourceBundleRepositoryMapping[] | undefined {
const repoUrls = [resourceBundleRef.repoUrl, ...resourceBundleRef.bundles.map((bundle) => bundle.repoUrl ?? resourceBundleRef.repoUrl)];
if (!repoUrls.some((repoUrl) => githubRepositoryIdentity(repoUrl) !== null)) return undefined;
return parseResourceBundleRepositoryMappings(env);
}
function parseResourceBundleRepositoryMappings(env: NodeJS.ProcessEnv): readonly ResourceBundleRepositoryMapping[] {
const envName = "AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON";
const raw = optionalNonEmpty(env[envName]);
if (!raw) return [];
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch (error) {
throw repositoryMappingError("resource-bundle-repositories-invalid-json", `${envName} is not valid JSON`, {
error: fileErrorSummary(error),
});
}
if (!Array.isArray(parsed)) {
throw repositoryMappingError("resource-bundle-repositories-invalid-shape", `${envName} must be an array`, {
actualType: parsed === null ? "null" : typeof parsed,
});
}
return parsed.map((entry, index) => {
if (!isJsonObject(entry)) {
throw repositoryMappingError("resource-bundle-repository-invalid-entry", `${envName}[${index}] must be an object`, { index });
}
const repository = optionalNonEmpty(entry.repository);
const normalizedRepository = repository ? normalizeRepositoryIdentity(repository) : null;
if (!repository || !normalizedRepository) {
throw repositoryMappingError("resource-bundle-repository-invalid-entry", `${envName}[${index}].repository must be an owner/repo identity`, { index });
}
const readUrl = optionalNonEmpty(entry.readUrl);
if (!readUrl || !isSupportedRepositoryReadUrl(readUrl)) {
throw repositoryMappingError("resource-bundle-repository-invalid-entry", `${envName}[${index}].readUrl must be a credential-free Git URL`, { index, repository: normalizedRepository });
}
return { repository, normalizedRepository, readUrl };
});
}
function resolveGitBundleFetchSourceFromMappings(repository: string, mappings: readonly ResourceBundleRepositoryMapping[]): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
const matches = mappings.filter((mapping) => mapping.normalizedRepository === repository);
if (matches.length === 0) {
throw repositoryMappingError("resource-bundle-repository-zero-match", `resource bundle repository ${repository} has no declared read URL`, {
repository,
mappingCount: mappings.length,
matchCount: 0,
});
}
if (matches.length !== 1) {
throw repositoryMappingError("resource-bundle-repository-multiple-matches", `resource bundle repository ${repository} has multiple declared read URLs`, {
repository,
mappingCount: mappings.length,
matchCount: matches.length,
});
}
return { fetchRepoUrl: matches[0]!.readUrl, mirrorUsed: true };
}
function repositoryMappingError(reason: string, message: string, details: JsonRecord): AgentRunError {
return new AgentRunError("schema-invalid", message, {
httpStatus: 400,
details: {
reason,
environment: "AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON",
...details,
valuesPrinted: false,
},
});
}
function isSupportedRepositoryReadUrl(value: string): boolean {
if (/\s/u.test(value)) return false;
if (/^git@[^:/\s]+:.+/u.test(value)) return true;
try {
const parsed = new URL(value);
if (!parsed.hostname || parsed.password) return false;
if (parsed.username && !(parsed.protocol === "ssh:" && parsed.username === "git")) return false;
return ["http:", "https:", "ssh:", "git:"].includes(parsed.protocol);
} catch {
return false;
}
}
function githubRepositoryIdentity(repoUrl: string): string | null {
const githubPath = githubRepoPath(repoUrl);
return githubPath ? normalizeRepositoryIdentity(githubPath) : null;
}
function normalizeRepositoryIdentity(value: string): string | null {
const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/u.exec(value.trim());
if (!match) return null;
const owner = match[1]?.toLowerCase();
const repo = match[2]?.toLowerCase();
return owner && repo ? `${owner}/${repo}` : null;
}
function githubRepoPath(repoUrl: string): string | null {
+22 -3
View File
@@ -37,7 +37,11 @@ const selfTest: SelfTestCase = async (context) => {
const combinedToolCredentials = [...githubToolCredentials, ...unideskSshToolCredentials, ...githubSshToolCredentials];
const item = await createRunWithCommand(client, { ...context, toolCredentials: combinedToolCredentials }, "job smoke", "selftest-job-render", 15_000);
const baseRun = await client.get(`/api/v1/runs/${item.runId}`) as RunRecord;
const rendered = renderRunnerJobDryRun({
const repositoryMappings = JSON.stringify([{ repository: "pikasTech/unidesk", readUrl: "http://gitea.example.test/mirrors/pikasTech-unidesk.git" }]);
const rendered = withProcessEnv({
AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON: repositoryMappings,
AGENTRUN_GIT_MIRROR_BASE_URL: "http://legacy-base.example.test",
}, () => renderRunnerJobDryRun({
run: baseRun,
commandId: item.commandId,
managerUrl: server.baseUrl,
@@ -46,7 +50,7 @@ const selfTest: SelfTestCase = async (context) => {
attemptId: "attempt_selftest",
sourceCommit: "self-test",
transientEnv: [{ name: "HWLAB_API_KEY", value: "hwl_live_selftest", sensitive: true }],
});
}));
assert.equal(rendered.dryRun, true);
assert.equal(rendered.mutation, false);
assert.equal(((rendered.retention as JsonRecord).ttlSecondsAfterFinished), 604_800);
@@ -60,6 +64,8 @@ const selfTest: SelfTestCase = async (context) => {
assertRunnerJobUsesG14EgressProxy(rendered.manifest as JsonRecord);
assertRunnerJobUsesBoundedGitTransport(rendered);
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "AGENTRUN_CODEX_SHELL_SANDBOX"), "danger-full-access");
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON"), repositoryMappings);
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "AGENTRUN_GIT_MIRROR_BASE_URL"), undefined);
assert.deepEqual(runnerEnvSecretKeyRef(rendered.manifest as JsonRecord, "AGENTRUN_API_KEY"), runnerApiKeySecretRef);
assert.deepEqual(rendered.runnerApiKeySecretRef, { ...runnerApiKeySecretRef, valuesPrinted: false });
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_API_KEY"), "REDACTED");
@@ -581,7 +587,7 @@ process.exit(1);
assert.equal(envMap.get("AGENTRUN_SESSION_PVC_NAMESPACE"), "agentrun-v01");
assert.equal(envMap.get("AGENTRUN_SESSION_PVC_MOUNT_PATH"), "/home/agentrun/.agentrun-sessions/sessions");
assert.equal(envMap.get("AGENTRUN_CODEX_ROLLOUT_SUBDIR"), "sessions");
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-codex-shell-sandbox-env", "runner-k8s-job-g14-egress-proxy-env", "runner-k8s-job-bounded-git-transport-env", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-minimax-m3-profile-dry-run", "runner-k8s-job-dsflash-go-profile-dry-run", "runner-k8s-job-dsflash-go-legacy-secretref-normalized", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-transient-env-secretref", "runner-job-tool-credential-env", "runner-job-unidesk-ssh-tool-credential-env", "runner-job-tool-credential-volume", "runner-job-resource-credential-volume", "runner-job-unidesk-ssh-endpoint-auto-env", "runner-job-unidesk-ssh-transient-env-denied", "runner-job-pre-create-retention", "runner-job-pre-create-retention-protects-stale-active", "runner-k8s-job-session-pvc-volume-and-env"] };
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-codex-shell-sandbox-env", "runner-k8s-job-resource-bundle-repositories-env", "runner-k8s-job-g14-egress-proxy-env", "runner-k8s-job-bounded-git-transport-env", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-minimax-m3-profile-dry-run", "runner-k8s-job-dsflash-go-profile-dry-run", "runner-k8s-job-dsflash-go-legacy-secretref-normalized", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-transient-env-secretref", "runner-job-tool-credential-env", "runner-job-unidesk-ssh-tool-credential-env", "runner-job-tool-credential-volume", "runner-job-resource-credential-volume", "runner-job-unidesk-ssh-endpoint-auto-env", "runner-job-unidesk-ssh-transient-env-denied", "runner-job-pre-create-retention", "runner-job-pre-create-retention-protects-stale-active", "runner-k8s-job-session-pvc-volume-and-env"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
@@ -589,6 +595,19 @@ process.exit(1);
export default selfTest;
function withProcessEnv<T>(values: Record<string, string>, operation: () => T): T {
const previous = new Map(Object.keys(values).map((name) => [name, process.env[name]]));
for (const [name, value] of Object.entries(values)) process.env[name] = value;
try {
return operation();
} finally {
for (const [name, value] of previous) {
if (value === undefined) delete process.env[name];
else process.env[name] = value;
}
}
}
function runnerEnvEntry(manifest: JsonRecord, name: string): JsonRecord | undefined {
const spec = manifest.spec as JsonRecord;
const template = spec.template as JsonRecord;
+7 -6
View File
@@ -300,13 +300,14 @@ const selfTest: SelfTestCase = async (context) => {
(((invalidGitIdentityDocument.spec as JsonRecord).executionPolicy as JsonRecord).gitIdentity as JsonRecord).email = "not-an-email";
assert.throws(() => validateAipodSpec(invalidGitIdentityDocument, "selftest-invalid-git-identity"), /valid single-line email address/u);
const mirrored = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { baseUrl: "http://mirror.example.test/root/" }, {});
assert.equal(mirrored.fetchRepoUrl, "http://mirror.example.test/root/pikasTech/unidesk.git");
const repositoryMappings = JSON.stringify([{ repository: "pikasTech/unidesk", readUrl: "http://mirror.example.test/mirrors/pikasTech-unidesk.git" }]);
const mirrored = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON: repositoryMappings });
assert.equal(mirrored.fetchRepoUrl, "http://mirror.example.test/mirrors/pikasTech-unidesk.git");
assert.equal(mirrored.mirrorUsed, true);
const defaultMirror = resolveGitBundleFetchSource("https://github.com/pikasTech/unidesk.git", undefined, { AGENTRUN_GIT_MIRROR_BASE_URL: "http://mirror.example.test/base" });
assert.equal(defaultMirror.fetchRepoUrl, "http://mirror.example.test/base/pikasTech/unidesk.git");
const defaultMirror = resolveGitBundleFetchSource("https://github.com/pikasTech/unidesk.git", { AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON: repositoryMappings });
assert.equal(defaultMirror.fetchRepoUrl, "http://mirror.example.test/mirrors/pikasTech-unidesk.git");
assert.equal(defaultMirror.mirrorUsed, true);
const nonGithub = resolveGitBundleFetchSource("ssh://git@example.test/repo.git", { baseUrl: "http://mirror.example.test" }, {});
const nonGithub = resolveGitBundleFetchSource("ssh://git@example.test/repo.git", { AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON: "invalid-json-is-ignored-for-non-github" });
assert.equal(nonGithub.fetchRepoUrl, "ssh://git@example.test/repo.git");
assert.equal(nonGithub.mirrorUsed, false);
assert.equal(classifyGitStderr("fatal: repository 'http://mirror.example.test/pikasTech/agent_skills.git/' not found\n"), "repository-not-found");
@@ -415,7 +416,7 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal((storedTask.payload as JsonRecord).model, "gpt-5.6-sol");
assert.equal((storedTask.payload as JsonRecord).reasoningEffort, "medium");
assert.deepEqual((storedTask.payload as JsonRecord).modelConfig, taskPayload.modelConfig);
return { name: "aipod-spec", tests: ["aipod-spec-yaml-parser-runtime-compatible", "aipod-spec-artificer-image-ref-render", "aipod-spec-artificer-release-runtime-authority", "aipod-spec-multi-upstream-secret-summary", "aipod-spec-legacy-single-upstream-compatible", "aipod-spec-model-defaults", "aipod-spec-model-reasoning-overrides", "aipod-spec-model-default-fallback", "aipod-spec-grok-exact-route", "aipod-spec-selected-provider-only", "aipod-spec-invalid-route-fail-closed", "aipod-spec-control-override-fail-closed", "aipod-spec-payload-control-bypass-fail-closed", "aipod-spec-durable-task-model-snapshot", "aipod-spec-primary-workspace-contract", "aipod-spec-artificer-github-url-render", "aipod-spec-artificer-github-ssh-required-keys", "aipod-spec-artificer-private-repository-override", "aipod-spec-artificer-private-repository-override-runner-projection", "aipod-spec-git-mirror-url", "git-fetch-stderr-classification", "resource-bundle-source-authority-validation", "resource-bundle-target-conflict-validation", "queue-submit-aipod-dry-run", "session-send-aipod-model-dry-run", "session-send-aipod-grok-dry-run", "session-send-aipod-cancelled-durable-identity", "session-send-aipod-boundary-conflict-fail-closed", "aipod-cli-help"] };
return { name: "aipod-spec", tests: ["aipod-spec-yaml-parser-runtime-compatible", "aipod-spec-artificer-image-ref-render", "aipod-spec-artificer-release-runtime-authority", "aipod-spec-multi-upstream-secret-summary", "aipod-spec-legacy-single-upstream-compatible", "aipod-spec-model-defaults", "aipod-spec-model-reasoning-overrides", "aipod-spec-model-default-fallback", "aipod-spec-grok-exact-route", "aipod-spec-selected-provider-only", "aipod-spec-invalid-route-fail-closed", "aipod-spec-control-override-fail-closed", "aipod-spec-payload-control-bypass-fail-closed", "aipod-spec-durable-task-model-snapshot", "aipod-spec-primary-workspace-contract", "aipod-spec-artificer-github-url-render", "aipod-spec-artificer-github-ssh-required-keys", "aipod-spec-artificer-private-repository-override", "aipod-spec-artificer-private-repository-override-runner-projection", "aipod-spec-resource-bundle-repository-map", "git-fetch-stderr-classification", "resource-bundle-source-authority-validation", "resource-bundle-target-conflict-validation", "queue-submit-aipod-dry-run", "session-send-aipod-model-dry-run", "session-send-aipod-grok-dry-run", "session-send-aipod-cancelled-durable-identity", "session-send-aipod-boundary-conflict-fail-closed", "aipod-cli-help"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
@@ -91,7 +91,10 @@ async function runInternalHttpMirrorMaterializationCase(context: Parameters<type
};
const workspaceRef: WorkspaceRef = { kind: "opaque", path: "." };
const materialized = await withGenericRunnerGitEnvironment(async () => await materializeResourceBundle(resourceBundleRef, workspaceRef, {
AGENTRUN_GIT_MIRROR_BASE_URL: mirrorBaseUrl,
AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON: JSON.stringify([{
repository: "pikasTech/agentrun-mirror-probe",
readUrl: `${mirrorBaseUrl}/pikasTech/agentrun-mirror-probe.git`,
}]),
AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "github-ssh-http-mirror-assembly"),
AGENTRUN_WORKSPACE_PATH: workspacePath,
AGENTRUN_RUN_ID: "run_github_ssh_http_mirror_selftest",
@@ -99,8 +102,9 @@ async function runInternalHttpMirrorMaterializationCase(context: Parameters<type
assert.ok(materialized);
assert.equal(materialized.event.mirrorUsed, true);
assert.equal(String(materialized.event.fetchRepoUrl).startsWith(`${mirrorBaseUrl}/`), true);
assert.deepEqual(materialized.event.repositoryAuthority, { repository: "pikastech/agentrun-mirror-probe", mode: "exact-read-url", valuesPrinted: false });
assert.equal(((materialized.event.gitTransport as JsonRecord).scope), "resource-bundle");
assert.equal(await readFile(path.join(workspacePath, "mirror-probe.txt"), "utf8"), "internal http mirror remains available\n");
assert.equal(await readFile(path.join(workspacePath, "probe.txt"), "utf8"), "internal http mirror remains available\n");
} finally {
await new Promise<void>((resolve) => mirrorServer.close(() => resolve()));
}
@@ -0,0 +1,74 @@
import assert from "node:assert/strict";
import { AgentRunError } from "../../common/errors.js";
import { resolveGitBundleFetchSource } from "../../runner/resource-bundle.js";
import type { SelfTestCase } from "../harness.js";
const repositoriesEnvName = "AGENTRUN_RESOURCE_BUNDLE_REPOSITORIES_JSON";
const exactReadUrl = "http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-unidesk.git";
const selfTest: SelfTestCase = async () => {
const exactMapping = JSON.stringify([
{ repository: "pikasTech/unidesk", readUrl: exactReadUrl },
{ repository: "pikasTech/agentrun", readUrl: "http://gitea.example.test/mirrors/pikasTech-agentrun.git" },
]);
const exact = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { [repositoriesEnvName]: exactMapping });
assert.deepEqual(exact, { fetchRepoUrl: exactReadUrl, mirrorUsed: true });
const normalized = resolveGitBundleFetchSource("https://github.com/PIKASTECH/UNIDESK.git", { [repositoriesEnvName]: exactMapping });
assert.deepEqual(normalized, { fetchRepoUrl: exactReadUrl, mirrorUsed: true });
assertMappingFailure(
() => resolveGitBundleFetchSource("git@github.com:pikasTech/missing.git", { [repositoriesEnvName]: exactMapping }),
"resource-bundle-repository-zero-match",
);
const duplicateMapping = JSON.stringify([
{ repository: "pikasTech/unidesk", readUrl: exactReadUrl },
{ repository: "PIKASTECH/UNIDESK", readUrl: "http://gitea.example.test/mirrors/duplicate.git" },
]);
assertMappingFailure(
() => resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { [repositoriesEnvName]: duplicateMapping }),
"resource-bundle-repository-multiple-matches",
);
assertMappingFailure(
() => resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { [repositoriesEnvName]: "{" }),
"resource-bundle-repositories-invalid-json",
);
assertMappingFailure(
() => resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { [repositoriesEnvName]: JSON.stringify([{ repository: "pikasTech/unidesk", readUrl: "https://user:password@gitea.example.test/repo.git" }]) }),
"resource-bundle-repository-invalid-entry",
);
const nonGithubUrl = "ssh://git@gitea.example.test/mirrors/pikasTech-unidesk.git";
assert.deepEqual(
resolveGitBundleFetchSource(nonGithubUrl, { [repositoriesEnvName]: "invalid-json-is-not-consumed" }),
{ fetchRepoUrl: nonGithubUrl, mirrorUsed: false },
);
return {
name: "resource-bundle-repository-map",
tests: [
"resource-bundle-repository-exact-read-url",
"resource-bundle-repository-normalized-github-identity",
"resource-bundle-repository-zero-match-typed-failure",
"resource-bundle-repository-multiple-match-typed-failure",
"resource-bundle-repositories-invalid-json-typed-failure",
"resource-bundle-repository-invalid-entry-typed-failure",
"resource-bundle-non-github-url-preserved",
],
};
};
function assertMappingFailure(operation: () => unknown, reason: string): void {
assert.throws(operation, (error: unknown) => {
assert.ok(error instanceof AgentRunError);
assert.equal(error.failureKind, "schema-invalid");
assert.equal(error.details?.reason, reason);
assert.equal(error.details?.environment, repositoriesEnvName);
assert.equal(error.details?.valuesPrinted, false);
return true;
});
}
export default selfTest;