fix: 让 gitbundle 自动使用 G14 git mirror

This commit is contained in:
AgentRun Codex
2026-06-11 16:48:50 +08:00
parent 8cf6534bec
commit e624835bc8
8 changed files with 34 additions and 61 deletions
-2
View File
@@ -76,8 +76,6 @@ spec:
kind: gitbundle
repoUrl: git@github.com:pikasTech/unidesk.git
ref: master
gitMirror:
enabled: false
bundles:
- name: unidesk-skills
subpath: .agents/skills
+7 -9
View File
@@ -93,23 +93,21 @@ imageRef:
- 通过 `toolCredentials` 获取 GitHub Issue/PR 写入 token`agentrun-v01-tool-github-pr` -> env `GH_TOKEN`
- 通过 `toolCredentials` 获取 UniDesk SSH 透传 token`agentrun-v01-tool-unidesk-ssh` -> env `UNIDESK_SSH_CLIENT_TOKEN`
- 通过 `toolCredentials` 获取 GitHub SSH 凭据:`agentrun-v01-tool-github-ssh` -> volume `/home/agentrun/.ssh`
- 通过 GitHub SSH 直接 checkout 多仓库 gitbundle,装配 UniDesk repo 中的 `unidesk-*` skills、AgentRun repo 中的 `tools/trans``tools/tran``tools/apply_patch`,以及 `agent_skills` repo 中的 `dad-dev``cli-spec``docs-spec``git-spec`Artificer 默认不依赖 G14 git mirror 的覆盖范围
- 通过声明 GitHub repo URL 的多仓库 gitbundle,装配 UniDesk repo 中的 `unidesk-*` skills、AgentRun repo 中的 `tools/trans``tools/tran``tools/apply_patch`,以及 `agent_skills` repo 中的 `dad-dev``cli-spec``docs-spec``git-spec`runner 物化阶段自动把 GitHub URL 改写到 G14 git mirrorArtificer 规格本身不声明 mirror 开关或 mirror base URL
- 通过 `requiredSkills[]` 在 runner 启动 backend 前校验 `dad-dev` 与 UniDesk 关键 skills 已被 materialize;缺失时必须 `required-skill-unavailable`,不得使用模型默认 skill 猜测。
- Artificer 默认是 session-capable agent。`render`/`queue submit --aipod Artificer` 在调用方没有显式传入 `sessionRef` 时,必须生成稳定的默认 `SessionRef``sessionId` 使用 `sess_artificer_<24 hex>` 形态,`conversationId` 默认等于 `sessionId`,并把该引用写入 Queue task/run 装配输入;后续 follow-up、补测、reviewer feedback 和 manager recovery 优先继续同一个 session,而不是每次新开 session。
## Git mirror
`resourceBundleRef.gitMirror` 用于把 GitHub repo URL 改写为 G14 git mirror read URL,提高 runner checkout 的稳定性和速度
Git mirror 是 AgentRun/G14 基础设施能力,不是 AipodSpec 或 Queue task 规格能力。`AipodSpec.spec.resourceBundleRef` 必须继续声明无明文 credential 的 GitHub repo URL;不得在 AipodSpec、Queue task、prompt 或业务 adapter 中声明 `gitMirror`、mirror base URL、direct/mirror 开关或 per-agent mirror 策略
规则:
- `enabled` 缺省为 `true`;显式 `false` 时不改写
- `baseUrl` 必须是无 credentials、query、fragment 的 HTTP(S) URL;未设置时 runner 使用 `AGENTRUN_GIT_MIRROR_BASE_URL`,再缺省为 `http://git-mirror-http.devops-infra.svc.cluster.local`
- runner 在 materialization 阶段自动把 GitHub URL 改写为 G14 git mirror read URL;基础 URL 来自 `AGENTRUN_GIT_MIRROR_BASE_URL`缺省为 `http://git-mirror-http.devops-infra.svc.cluster.local`
- 支持 `git@github.com:owner/repo.git``ssh://git@github.com/owner/repo.git``ssh://git@ssh.github.com:443/owner/repo.git``https://github.com/owner/repo.git`
- 非 GitHub URL 不改写,仍按原 `repoUrl` fetch。
- materialization event 必须输出 `repoUrl``fetchRepoUrl``mirrorUsed``mirrorBaseUrl`、requested ref/commit 和实际 commit;不得输出 credential 值
Artificer 默认 `gitMirror.enabled=false`,依靠 `agentrun-v01-tool-github-ssh` 投影的 SSH 凭据直接拉取 `unidesk``agentrun``agent_skills`。只有明确确认目标 repo 已纳入 G14 mirror cache 的规格,才应显式启用 `gitMirror`
- devops-infra mirror cache 必须覆盖 Artificer 默认 bundle 使用的 `pikasTech/unidesk``pikasTech/agentrun``pikasTech/agent_skills`;缺 cache 是基础设施缺口,不能通过修改 AipodSpec 直连 GitHub 来绕过
- materialization event 必须输出原始 `repoUrl`、实际 `fetchRepoUrl``mirrorUsed``mirrorBaseUrl`、requested ref/commit 和实际 commit;不得输出 credential 值。
## Tool credential projection
@@ -154,10 +152,10 @@ CLI
## 测试规格
- A1`config/aipods/artificer.yaml` 能被 manager list/show/renderrender 结果包含 `imageRef.kind=env-image-dockerfile``repoUrl``commitId``dockerfilePath``backendProfile=sub2api``model=gpt-5.5``reasoningEffort=xhigh`、provider SecretRef、GitHub PR token env projection、UniDesk SSH env projection、GitHub SSH volume projection、Artificer `gitMirror.enabled=false`、AgentRun runner tools gitbundle 和 gitbundle requiredSkills。
- A1`config/aipods/artificer.yaml` 能被 manager list/show/renderrender 结果包含 `imageRef.kind=env-image-dockerfile``repoUrl``commitId``dockerfilePath``backendProfile=sub2api``model=gpt-5.5``reasoningEffort=xhigh`、provider SecretRef、GitHub PR token env projection、UniDesk SSH env projection、GitHub SSH volume projection、`resourceBundleRef.gitMirror` 字段、AgentRun runner tools gitbundle 和 gitbundle requiredSkills。
- A2`queue submit --aipod Artificer --dry-run` 输出标准 `queue-submit-plan`,且 `idempotencyKey`、prompt 与 metadata 被保留。
- A2b`queue submit --aipod Artificer --dry-run``render Artificer` 在没有显式 `sessionRef` 时必须输出默认 `sessionRef.sessionId` / `conversationId`;显式传入 `sessionRef` 时不得覆盖。
- A3Artificer 默认 `gitMirror.enabled=false` 时 GitHub URL 保持 SSH fetch;显式启用 `gitMirror` GitHub URL 改写到 mirror base URL非 GitHub URL 不改写。
- A3Artificer 规格只声明 GitHub URL,不声明 `gitMirror`runner 默认把 GitHub URL 改写到 mirror base URL非 GitHub URL 不改写。请求体显式携带 `resourceBundleRef.gitMirror` 必须 schema-invalid,避免把基础设施策略下放到 AipodSpec。
- A4Aipod 启动/dispatch 能基于 `imageRef` 命中 artifact catalog / registry 并复用 digest-pinned env image;未命中时返回明确 build-required 或进入受控 CI/CD,不在 runner 任务内补装依赖。
- A5runner Job dry-run 支持 tool credential volume mount,并且 response/manifest/event 不泄漏 Secret 明文。
- A6`bun run check``bun run self-test` 必须覆盖 A1-A5。
+3 -3
View File
@@ -188,7 +188,7 @@ HWLAB Workbench 的 project/workspace 不属于 RuntimeAssembly 四要素,也
- P0 固定 `kind="gitbundle"`,输入只依赖 Git repo/ref 与 `bundles[]`runner 从 `resourceBundleRef.ref`、run 的 `workspaceRef.branch``HEAD` 解析实际 commit,再用该 materialized commit 作为内容身份。
- `commitId` 是可选 pin 或历史请求 hint;提供时必须是 full commit sha,但 HWLAB gitbundle 默认不得依赖 cloud-api/CI/CD rollout 注入的 commitId。
- `bundles[]` 每一项只允许 `{ name?, repoUrl?, ref?, commitId?, subpath, target_path }`;缺省 repo/ref/commit 继承顶层解析结果。
- `gitMirror` 可把 GitHub repo URL 改写到受控 mirror read URLevent/result 必须同时记录原始 `repoUrl`、实际 `fetchRepoUrl``mirrorUsed``mirrorBaseUrl`,不能只显示理论 repo。
- Git mirror 是基础设施能力,不是 `ResourceBundleRef` 字段。调用方只声明 GitHub `repoUrl`runner 在物化阶段自动改写到受控 mirror read URLevent/result 必须同时记录原始 `repoUrl`、实际 `fetchRepoUrl``mirrorUsed``mirrorBaseUrl`,不能只显示理论 repo。请求体中显式出现 `resourceBundleRef.gitMirror` 必须 schema-invalid,避免业务 spec 控制基础设施策略。
- `subpath` 必须留在 checkout 内,`target_path` 必须留在 runner workspace 内;runner 按 `subpath -> target_path` 复制文件或目录。
- `credentialRef` 只用于拉取私有 Git repo,不等同于 backend API KEY。
- 不支持上传文件、对象存储 artifact、任意 ConfigMap 文件袋、inline seed 或旧字段;旧 `toolAliases``skillRefs``workspaceFiles``subdir``sparsePaths` 输入必须直接 schema-invalid。
@@ -288,7 +288,7 @@ skill 只来自 gitbundle 复制进 workspace 的 `.agents/skills/<name>/SKILL.m
- checkout 只能进入允许 workspace 前缀,不能覆盖 `/app`、Secret projection、profile runtime home 或 session 目录。
- run payload 不携带文件正文、env dump、Secret value 或大型 artifact。
- 若提供 `bundles[]`,必须能看到每个 `subpath -> target_path` 的复制摘要;旧字段输入必须 schema-invalid。
- 若启用 `gitMirror`,必须能看到 GitHub URL 被改写到 mirror `fetchRepoUrl`;非 GitHub URL 必须保留原 fetch URL 并显示 `mirrorUsed=false`
- GitHub URL 必须默认被改写到 mirror `fetchRepoUrl`;非 GitHub URL 必须保留原 fetch URL 并显示 `mirrorUsed=false`;请求体显式 `resourceBundleRef.gitMirror` 必须被拒绝
- 若提供 `promptRefs`,必须能看到每个 prompt 的 `name/path/sha256/bytes/inject`,新 thread 首轮 `initialPromptInjected=true`resume turn `initialPromptInjected=false`
- 若 bundle 复制了 `.agents/skills`,必须能看到 skillDirs 聚合摘要、skill 名称、manifest hash/bytes 和来源 bundle;若提供 `requiredSkills`,必须看到成功路径的 requiredSkills hash/bytes,以及缺失路径的 `required-skill-unavailable` blocker。不能显示模型默认 skill 列表当作业务 skill。
@@ -311,6 +311,6 @@ skill 只来自 gitbundle 复制进 workspace 的 `.agents/skills/<name>/SKILL.m
| `ProfileRef` | 已实现/待 dsflash-go 真实主闭环 | `codex``deepseek``minimax-m3` 已通过 SecretRef、writable runtime home 和真实 stdio turn 验证;MiniMax-M3 已通过 HWLAB 显式 session 原入口复测。`dsflash-go` 已补齐 SecretRef/model catalog 装配、自测试和 legacy key 归一,仍需完成真实 runtime 与 HWLAB 原入口复测;后续只允许作为 profile/config/SecretRef/model catalog 选择,不新增直连 backend。 |
| `SessionRef` | 已实现最小持久化 | manager 持久化 `sessionId/conversationId/threadId`run 创建会解析既有 sessionrunner 按 threadId resumesession 不保存 credential 文件,TTL/GC 后续细化。 |
| `SessionRef` | v0.1.1 已实现/已通过 HWLAB v0.2 原入口复测 | manager 持久化 `sessionId/conversationId/threadId` + 每个 session 绑 RWO PVC`agentrun-v01-session-<sessionId>`),runner Job 把 PVC 直接挂到 `${CODEX_HOME}/<codex_rollout_subdir>`codex app-server 自己落盘;runner pod 删除后 replacement runner 仍复用同一 SessionRef/PVC/thread,禁止 copy/restore、replacement threadId 和 fake resume。 |
| `ResourceBundleRef` | 已实现 `kind="gitbundle"` materialization/promptRefs/tools/skillDirs/requiredSkills 装配 | `repoUrl + ref/materialized commit + bundles[]` 已进入 run schema 和 runner checkoutworkspace 受 `AGENTRUN_WORKSPACE_ROOT` 限制,event/result 记录 requested ref/commit、actual commit、tree/workspace/bundles 摘要;`gitMirror`把 GitHub URL 改写到受控 mirror 并记录 `fetchRepoUrl/mirrorUsed``tools/` PATH、`promptRefs` thread-start 注入、`.agents/skills` 目录发现和 required skill 校验已实现。 |
| `ResourceBundleRef` | 已实现 `kind="gitbundle"` materialization/promptRefs/tools/skillDirs/requiredSkills 装配 | `repoUrl + ref/materialized commit + bundles[]` 已进入 run schema 和 runner checkoutworkspace 受 `AGENTRUN_WORKSPACE_ROOT` 限制,event/result 记录 requested ref/commit、actual commit、tree/workspace/bundles 摘要;runner 默认把 GitHub URL 改写到受控 mirror 并记录 `fetchRepoUrl/mirrorUsed`,请求体不允许携带 `gitMirror``tools/` PATH、`promptRefs` thread-start 注入、`.agents/skills` 目录发现和 required skill 校验已实现。 |
| `toolCredentials` | 已实现 env 与 volume projection | GitHub PR、GitHub SSH 和 UniDesk SSH passthrough 等 agent shell/tool 授权通过装配 SPEC 的 SecretRef 进入 runnerv0.1 支持 `tool=github``tool=unidesk-ssh``projection.kind=env|volume`runner Job 使用 `valueFrom.secretKeyRef` 或只读 Secret volume 注入,不用 `transientEnv` 绕过。 |
| `transientEnv` | 已实现 per-job SecretRef 投影 | Queue dispatch 和 runner-job API 支持短期 runtime env;正式 Kubernetes Job 先创建本次 Job 专属 Secret,再用 `valueFrom.secretKeyRef` 注入 runner envresponse/event/trace 只显示 env names、Secret metadata 和 `valuesPrinted=false`。 |
-1
View File
@@ -250,7 +250,6 @@ function summarizeResourceBundle(resourceBundleRef: ResourceBundleRef | null): J
repoUrl: resourceBundleRef.repoUrl,
ref: resourceBundleRef.ref ?? null,
commitId: resourceBundleRef.commitId ?? null,
gitMirror: resourceBundleRef.gitMirror ? { enabled: resourceBundleRef.gitMirror.enabled ?? true, baseUrl: resourceBundleRef.gitMirror.baseUrl ?? null, valuesPrinted: false } : { enabled: false, baseUrl: null, valuesPrinted: false },
bundles: { count: resourceBundleRef.bundles.length, items: resourceBundleRef.bundles.map((item) => ({ name: item.name ?? null, repoUrl: item.repoUrl ?? resourceBundleRef.repoUrl, ref: item.ref ?? resourceBundleRef.ref ?? null, commitId: item.commitId ?? resourceBundleRef.commitId ?? null, subpath: item.subpath, targetPath: item.targetPath, valuesPrinted: false })), valuesPrinted: false },
requiredSkills: { count: resourceBundleRef.requiredSkills?.length ?? 0, names: resourceBundleRef.requiredSkills?.map((item) => item.name) ?? [], valuesPrinted: false },
promptRefs: { count: resourceBundleRef.promptRefs?.length ?? 0, names: resourceBundleRef.promptRefs?.map((item) => item.name) ?? [], valuesPrinted: false },
-4
View File
@@ -84,10 +84,6 @@ export interface ResourceBundleRef extends JsonRecord {
repoUrl: string;
commitId?: string;
ref?: string;
gitMirror?: {
enabled?: boolean;
baseUrl?: string;
};
bundles: GitBundleItemRef[];
promptRefs?: Array<{
name: string;
+2 -24
View File
@@ -89,8 +89,7 @@ export function validateResourceBundleRef(value: unknown): ResourceBundleRef | n
if (commitId) validateCommitId(commitId, "resourceBundleRef.commitId");
const ref = validateGitRef(record.ref, "resourceBundleRef.ref");
rejectLegacyResourceBundleFields(record);
const gitMirror = validateGitMirror(record.gitMirror);
const result: ResourceBundleRef = { kind: "gitbundle", repoUrl, ...(commitId ? { commitId } : {}), ...(ref ? { ref } : {}), ...(gitMirror ? { gitMirror } : {}), bundles: validateResourceGitBundles(record.bundles, repoUrl, commitId, ref) };
const result: ResourceBundleRef = { kind: "gitbundle", repoUrl, ...(commitId ? { commitId } : {}), ...(ref ? { ref } : {}), bundles: validateResourceGitBundles(record.bundles, repoUrl, commitId, ref) };
if (record.promptRefs !== undefined) result.promptRefs = validateResourcePromptRefs(record.promptRefs);
if (record.requiredSkills !== undefined) result.requiredSkills = validateResourceRequiredSkills(record.requiredSkills);
if (record.submodules !== undefined && record.submodules !== false) throw new AgentRunError("schema-invalid", "resourceBundleRef.submodules can only be false in v0.1", { httpStatus: 400 });
@@ -101,29 +100,8 @@ export function validateResourceBundleRef(value: unknown): ResourceBundleRef | n
return result;
}
function validateGitMirror(value: unknown): NonNullable<ResourceBundleRef["gitMirror"]> | undefined {
if (value === undefined) return undefined;
const record = asRecord(value, "resourceBundleRef.gitMirror");
const enabled = record.enabled === undefined ? true : record.enabled;
if (typeof enabled !== "boolean") throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.enabled must be boolean", { httpStatus: 400 });
const baseUrl = optionalString(record.baseUrl);
if (baseUrl) validateGitMirrorBaseUrl(baseUrl);
return { enabled, ...(baseUrl ? { baseUrl } : {}) };
}
function validateGitMirrorBaseUrl(value: string): void {
let parsed: URL;
try {
parsed = new URL(value);
} catch {
throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.baseUrl must be an http(s) URL", { httpStatus: 400 });
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.baseUrl must use http or https", { httpStatus: 400 });
if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new AgentRunError("schema-invalid", "resourceBundleRef.gitMirror.baseUrl must not include credentials, query, or fragment", { httpStatus: 400 });
}
function rejectLegacyResourceBundleFields(record: JsonRecord): void {
for (const field of ["toolAliases", "skillRefs", "workspaceFiles", "subdir", "sparsePaths"] as const) {
for (const field of ["toolAliases", "skillRefs", "workspaceFiles", "subdir", "sparsePaths", "gitMirror"] as const) {
if (record[field] !== undefined) throw new AgentRunError("schema-invalid", `resourceBundleRef.${field} is removed; use resourceBundleRef.bundles[] with kind=gitbundle`, { httpStatus: 400 });
}
}
+12 -10
View File
@@ -55,7 +55,6 @@ interface GitCheckout {
}
interface GitMirrorConfig {
enabled: true;
baseUrl: string;
}
@@ -146,13 +145,13 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
}
function gitMirrorConfig(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
return normalizeGitMirrorConfig(resourceBundleRef.gitMirror, env);
void resourceBundleRef;
return defaultGitMirrorConfig(env);
}
function normalizeGitMirrorConfig(gitMirror: ResourceBundleRef["gitMirror"] | GitMirrorConfig | undefined, env: NodeJS.ProcessEnv): GitMirrorConfig | undefined {
if (!gitMirror || gitMirror.enabled === false) return undefined;
const baseUrl = optionalNonEmpty(gitMirror.baseUrl) ?? optionalNonEmpty(env.AGENTRUN_GIT_MIRROR_BASE_URL) ?? "http://git-mirror-http.devops-infra.svc.cluster.local";
return { enabled: true, baseUrl: baseUrl.replace(/\/+$/u, "") };
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): GitBundleSource {
@@ -199,17 +198,20 @@ 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 ? { enabled: true, baseUrl: source.gitMirror.baseUrl } : null };
return { repoUrl: source.repoUrl, commitId: source.commitId ?? null, ref: source.ref ?? null, gitMirror: source.gitMirror ? { baseUrl: source.gitMirror.baseUrl } : null };
}
export function resolveGitBundleFetchSource(repoUrl: string, gitMirror?: ResourceBundleRef["gitMirror"] | GitMirrorConfig, env: NodeJS.ProcessEnv = process.env): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
const mirror = normalizeGitMirrorConfig(gitMirror, env);
if (!mirror) return { fetchRepoUrl: repoUrl, mirrorUsed: false };
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, "");
}
function gitFetchSource(source: GitBundleSource): { fetchRepoUrl: string; mirrorUsed: boolean; mirrorBaseUrl?: string } {
return resolveGitBundleFetchSource(source.repoUrl, source.gitMirror);
}
+10 -8
View File
@@ -5,6 +5,7 @@ import { ManagerClient } from "../../mgr/client.js";
import { startManagerServer } from "../../mgr/server.js";
import { MemoryAgentRunStore } from "../../mgr/store.js";
import type { JsonRecord } from "../../common/types.js";
import { validateResourceBundleRef } from "../../common/validation.js";
import { resolveGitBundleFetchSource } from "../../runner/resource-bundle.js";
import { assertNoSecretLeak, loadArtificerImageRef, type SelfTestCase } from "../harness.js";
@@ -30,7 +31,7 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal(shownImageRef.repoUrl, "git@github.com:pikasTech/agentrun.git");
assert.equal(shownImageRef.commitId, artificerImageRef.commitId);
assert.equal(shownImageRef.dockerfilePath, "deploy/container/Containerfile");
assert.equal(((shownItem.resourceBundleRef as JsonRecord).gitMirror as JsonRecord).enabled, false);
assert.equal(Object.hasOwn(shownItem.resourceBundleRef as JsonRecord, "gitMirror"), false);
const rendered = await client.post("/api/v1/aipod-specs/Artificer/render", { prompt: "处理 pikasTech/unidesk#245", idempotencyKey: "selftest-aipod-artificer" }) as JsonRecord;
assert.equal(rendered.action, "aipod-spec-render");
@@ -58,7 +59,7 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal(toolCredentials.some((item) => item.tool === "unidesk-ssh" && ((item.projection as JsonRecord).envName) === "UNIDESK_SSH_CLIENT_TOKEN"), true);
assert.equal(toolCredentials.some((item) => item.tool === "github" && ((item.projection as JsonRecord).kind) === "volume" && ((item.projection as JsonRecord).mountPath) === "/home/agentrun/.ssh"), true);
const bundle = task.resourceBundleRef as JsonRecord;
assert.equal(((bundle.gitMirror as JsonRecord).enabled), false);
assert.equal(Object.hasOwn(bundle, "gitMirror"), false);
const bundles = bundle.bundles as JsonRecord[];
const toolBundle = bundles.find((item) => item.name === "agentrun-runner-tools") as JsonRecord | undefined;
assert.equal(toolBundle?.repoUrl, "git@github.com:pikasTech/agentrun.git");
@@ -69,15 +70,16 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal((bundle.requiredSkills as JsonRecord[]).some((item) => item.name === "unidesk-gh"), true);
assertNoSecretLeak(rendered);
const mirrored = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { enabled: true, baseUrl: "http://mirror.example.test/root/" }, {});
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");
assert.equal(mirrored.mirrorUsed, true);
const disabledMirror = resolveGitBundleFetchSource("git@github.com:pikasTech/unidesk.git", { enabled: false, baseUrl: "http://mirror.example.test/root/" }, {});
assert.equal(disabledMirror.fetchRepoUrl, "git@github.com:pikasTech/unidesk.git");
assert.equal(disabledMirror.mirrorUsed, false);
const nonGithub = resolveGitBundleFetchSource("ssh://git@example.test/repo.git", { enabled: true, baseUrl: "http://mirror.example.test" }, {});
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");
assert.equal(defaultMirror.mirrorUsed, true);
const nonGithub = resolveGitBundleFetchSource("ssh://git@example.test/repo.git", { baseUrl: "http://mirror.example.test" }, {});
assert.equal(nonGithub.fetchRepoUrl, "ssh://git@example.test/repo.git");
assert.equal(nonGithub.mirrorUsed, false);
assert.throws(() => validateResourceBundleRef({ kind: "gitbundle", repoUrl: "git@github.com:pikasTech/unidesk.git", ref: "master", gitMirror: { enabled: false }, bundles: [{ subpath: ".", targetPath: "." }] }), /resourceBundleRef.gitMirror is removed/u);
const submitPlan = await runCliJson(context, server.baseUrl, ["queue", "submit", "--aipod", "Artificer", "--prompt", "处理 pikasTech/unidesk#245", "--idempotency-key", "selftest-aipod-cli", "--dry-run"]);
assert.equal(submitPlan.ok, true);
@@ -91,7 +93,7 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal(commands.some((item) => item.includes("aipod-specs render <name>")), true);
assert.equal(commands.some((item) => item.includes("queue submit --aipod <name>")), true);
assertNoSecretLeak(submitPlan);
return { name: "aipod-spec", tests: ["aipod-spec-yaml-parser-runtime-compatible", "aipod-spec-artificer-image-ref-render", "aipod-spec-artificer-direct-ssh-render", "aipod-spec-git-mirror-url", "queue-submit-aipod-dry-run", "aipod-cli-help"] };
return { name: "aipod-spec", tests: ["aipod-spec-yaml-parser-runtime-compatible", "aipod-spec-artificer-image-ref-render", "aipod-spec-artificer-github-url-render", "aipod-spec-git-mirror-url", "queue-submit-aipod-dry-run", "aipod-cli-help"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}