Merge pull request #295 from pikasTech/fix/294-resource-bundle-fetch
Pipelines as Code CI / agentrun-nc01-v02-ci-4805e02441f576ca19fae22a5815c7385f9ba110 Success

fix(v0.2): 补全 resource-bundle fetch 首错与任务失败投影
This commit is contained in:
Lyon
2026-07-12 00:12:53 +08:00
committed by GitHub
5 changed files with 170 additions and 27 deletions
+7
View File
@@ -363,10 +363,16 @@ async function enrichQueueTaskForRead(store: AgentRunStore, task: JsonRecord): P
const activeSession = await queueTaskActiveSession(store, task);
const supervisor = await queueTaskSupervisor(store, task, activeSession);
const activeBySession = activeSession?.active === true || stringJsonValue(activeSession?.activeRunId) !== null || stringJsonValue(activeSession?.activeCommandId) !== null;
const state = stringJsonValue(task.state);
const terminalFailure = state === "failed" || state === "blocked" || state === "cancelled";
const failureKind = terminalFailure ? stringJsonValue(supervisor?.failureKind) : null;
const failureMessage = terminalFailure ? stringJsonValue(supervisor?.failureMessage) : null;
return {
...task,
...(activeSession ? { activeSession } : {}),
...(supervisor ? { supervisor } : {}),
...(failureKind ? { failureKind } : {}),
...(failureMessage ? { failureMessage } : {}),
...(activeBySession ? { active: true, attentionState: "active-session" } : {}),
valuesPrinted: false,
};
@@ -401,6 +407,7 @@ async function queueTaskSupervisor(store: AgentRunStore, task: JsonRecord, activ
status: stringJsonValue(result.status),
terminalStatus: stringJsonValue(result.terminalStatus),
failureKind: stringJsonValue(result.failureKind),
failureMessage: boundedJsonString(result.failureMessage, 240),
diagnosis: asJsonRecord(result.diagnosis),
terminalClassification: terminalClassification ? compactTerminalClassification(terminalClassification) : null,
phase: stringJsonValue(liveness?.phase),
+116 -22
View File
@@ -65,6 +65,18 @@ interface GitBundleSource {
commitId?: string;
ref?: string;
gitMirror?: GitMirrorConfig;
credentialRefPresent: boolean;
}
interface GitBundleSourceContext extends JsonRecord {
kind: "resource-bundle" | "bundle";
index: number | null;
name: string | null;
}
interface GitSourceRef {
kind: "ref" | "commit";
value: string;
}
interface MaterializedGitBundle {
@@ -99,16 +111,16 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
const gitMirror = gitMirrorConfig(resourceBundleRef, env);
const defaultSource = defaultGitBundleSource(resourceBundleRef, env, gitMirror);
const checkoutCache = new Map<string, Promise<GitCheckout>>();
const checkoutFor = (source: GitBundleSource) => {
const checkoutFor = (source: GitBundleSource, sourceContext: GitBundleSourceContext) => {
const key = stableHash(gitSourceIdentity(source));
let checkout = checkoutCache.get(key);
if (!checkout) {
checkout = checkoutGitSource(checkoutRoot, source);
checkout = checkoutGitSource(checkoutRoot, source, sourceContext);
checkoutCache.set(key, checkout);
}
return checkout;
};
const defaultCheckout = await checkoutFor(defaultSource);
const defaultCheckout = await checkoutFor(defaultSource, { kind: "resource-bundle", index: null, name: null });
const materializedBundles = await materializeGitBundles(workspacePath, resourceBundleRef, defaultSource, defaultCheckout, checkoutFor);
const tools = await prepareGitBundleTools(workspacePath, env);
const skills = await discoverGitBundleSkills(workspacePath, materializedBundles);
@@ -168,37 +180,49 @@ function defaultGitMirrorConfig(env: NodeJS.ProcessEnv): GitMirrorConfig {
function defaultGitBundleSource(resourceBundleRef: ResourceBundleRef, env: NodeJS.ProcessEnv, gitMirror?: GitMirrorConfig): GitBundleSource {
const ref = optionalNonEmpty(resourceBundleRef.ref) ?? optionalNonEmpty(env.AGENTRUN_RESOURCE_BUNDLE_REF) ?? optionalNonEmpty(env.AGENTRUN_WORKSPACE_REF) ?? optionalNonEmpty(env.AGENTRUN_WORKSPACE_BRANCH);
if (ref) return { repoUrl: resourceBundleRef.repoUrl, ref, ...(gitMirror ? { gitMirror } : {}) };
const credentialRefPresent = resourceBundleRef.credentialRef !== undefined;
if (ref) return { repoUrl: resourceBundleRef.repoUrl, ref, credentialRefPresent, ...(gitMirror ? { gitMirror } : {}) };
const commitId = optionalNonEmpty(resourceBundleRef.commitId);
if (commitId) return { repoUrl: resourceBundleRef.repoUrl, commitId, ...(gitMirror ? { gitMirror } : {}) };
return { repoUrl: resourceBundleRef.repoUrl, ref: "HEAD", ...(gitMirror ? { gitMirror } : {}) };
if (commitId) return { repoUrl: resourceBundleRef.repoUrl, commitId, credentialRefPresent, ...(gitMirror ? { gitMirror } : {}) };
return { repoUrl: resourceBundleRef.repoUrl, ref: "HEAD", credentialRefPresent, ...(gitMirror ? { gitMirror } : {}) };
}
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;
if (ref) return { repoUrl, ref, ...(mirror ? { gitMirror: mirror } : {}) };
const credentialRefPresent = defaultSource.credentialRefPresent;
if (ref) return { repoUrl, ref, credentialRefPresent, ...(mirror ? { gitMirror: mirror } : {}) };
const commitId = optionalNonEmpty(bundle.commitId);
if (commitId) return { repoUrl, commitId, ...(mirror ? { gitMirror: mirror } : {}) };
if (commitId) return { repoUrl, commitId, credentialRefPresent, ...(mirror ? { gitMirror: mirror } : {}) };
if (repoUrl === defaultSource.repoUrl) return defaultSource;
if (defaultSource.ref) return { repoUrl, ref: defaultSource.ref, ...(mirror ? { gitMirror: mirror } : {}) };
if (defaultSource.commitId) return { repoUrl, commitId: defaultSource.commitId, ...(mirror ? { gitMirror: mirror } : {}) };
return { repoUrl, ref: "HEAD", ...(mirror ? { gitMirror: mirror } : {}) };
if (defaultSource.ref) return { repoUrl, ref: defaultSource.ref, credentialRefPresent, ...(mirror ? { gitMirror: mirror } : {}) };
if (defaultSource.commitId) return { repoUrl, commitId: defaultSource.commitId, credentialRefPresent, ...(mirror ? { gitMirror: mirror } : {}) };
return { repoUrl, ref: "HEAD", credentialRefPresent, ...(mirror ? { gitMirror: mirror } : {}) };
}
async function checkoutGitSource(checkoutRoot: string, source: GitBundleSource): Promise<GitCheckout> {
async function checkoutGitSource(checkoutRoot: string, source: GitBundleSource, sourceContext: GitBundleSourceContext): Promise<GitCheckout> {
const checkoutPath = path.join(checkoutRoot, stableHash(gitSourceIdentity(source)).slice(0, 16));
const fetch = gitFetchSource(source);
const sourceRef: GitSourceRef = source.ref ? { kind: "ref", value: source.ref } : { kind: "commit", value: source.commitId ?? "" };
const fetchOptions: GitCommandOptions = {
operation: "fetch",
repoUrl: source.repoUrl,
fetchRepoUrl: fetch.fetchRepoUrl,
mirrorUsed: fetch.mirrorUsed,
sourceRef,
sourceContext,
credentialRefPresent: source.credentialRefPresent,
};
await mkdir(checkoutPath, { recursive: true });
await git(["init"], checkoutPath);
await git(["remote", "remove", "origin"], checkoutPath, { allowFailure: true });
await git(["remote", "add", "origin", fetch.fetchRepoUrl], checkoutPath);
if (source.ref) {
await git(["fetch", "--depth", "1", "origin", source.ref], checkoutPath, { operation: "fetch", repoUrl: source.repoUrl, fetchRepoUrl: fetch.fetchRepoUrl });
await git(["fetch", "--depth", "1", "origin", source.ref], checkoutPath, fetchOptions);
await git(["checkout", "--detach", "FETCH_HEAD"], checkoutPath);
} else if (source.commitId) {
await git(["fetch", "--depth", "1", "origin", source.commitId], checkoutPath, { operation: "fetch", repoUrl: source.repoUrl, fetchRepoUrl: fetch.fetchRepoUrl });
await git(["fetch", "--depth", "1", "origin", source.commitId], checkoutPath, fetchOptions);
await git(["checkout", "--detach", source.commitId], checkoutPath);
} else {
throw new AgentRunError("schema-invalid", "gitbundle source must include repo ref or commit", { httpStatus: 400 });
@@ -246,11 +270,11 @@ function cleanGithubPath(owner: string, repo: string): string | null {
return `${cleanOwner}/${cleanRepo}`;
}
async function materializeGitBundles(workspacePath: string, resourceBundleRef: ResourceBundleRef, defaultSource: GitBundleSource, defaultCheckout: GitCheckout, checkoutFor: (source: GitBundleSource) => Promise<GitCheckout>): Promise<MaterializedGitBundle[]> {
async function materializeGitBundles(workspacePath: string, resourceBundleRef: ResourceBundleRef, defaultSource: GitBundleSource, defaultCheckout: GitCheckout, checkoutFor: (source: GitBundleSource, sourceContext: GitBundleSourceContext) => Promise<GitCheckout>): Promise<MaterializedGitBundle[]> {
const items: MaterializedGitBundle[] = [];
for (const [index, bundle] of resourceBundleRef.bundles.entries()) {
const gitSource = bundleGitSource(bundle, resourceBundleRef, defaultSource);
const checkout = gitSource === defaultSource ? defaultCheckout : await checkoutFor(gitSource);
const checkout = gitSource === defaultSource ? defaultCheckout : await checkoutFor(gitSource, { kind: "bundle", index, name: bundle.name ?? null });
const source = resolveBundlePath(checkout.checkoutPath, bundle.subpath, `bundles[${index}].subpath`);
const target = resolveWorkspaceTargetPath(workspacePath, bundle.targetPath, `bundles[${index}].target_path`);
let sourceStat;
@@ -563,7 +587,30 @@ function fileErrorSummary(error: unknown): JsonRecord {
return { code: typeof record.code === "string" ? record.code : null, message: typeof record.message === "string" ? redactText(record.message).slice(0, 300) : null };
}
async function git(args: string[], cwd: string, options: { allowFailure?: boolean; operation?: string; repoUrl?: string; fetchRepoUrl?: string } = {}): Promise<{ stdout: string; stderr: string }> {
interface GitCommandOptions {
allowFailure?: boolean;
operation?: string;
repoUrl?: string;
fetchRepoUrl?: string;
mirrorUsed?: boolean;
sourceRef?: GitSourceRef;
sourceContext?: GitBundleSourceContext;
credentialRefPresent?: boolean;
}
export function classifyGitStderr(stderr: string): string {
const text = stderr.trim();
if (/repository\s+['"].+['"]\s+not found|repository not found/iu.test(text)) return "repository-not-found";
if (/couldn't find remote ref|remote ref .+ not found|not our ref|unadvertised object/iu.test(text)) return "source-ref-not-found";
if (/authentication failed|permission denied|could not read username|access denied|http[^\n]*\b(?:401|403)\b/iu.test(text)) return "authentication-failed";
if (/could not resolve host|name or service not known|temporary failure in name resolution/iu.test(text)) return "dns-failed";
if (/failed to connect|connection refused|connection timed out|network is unreachable|no route to host/iu.test(text)) return "connection-failed";
if (/certificate|\bssl\b|\btls\b/iu.test(text)) return "tls-failed";
if (/early eof|rpc failed|remote end hung up|http[^\n]*\b5\d\d\b/iu.test(text)) return "transport-failed";
return text.length === 0 ? "stderr-empty" : "unclassified";
}
async function git(args: string[], cwd: string, options: GitCommandOptions = {}): Promise<{ stdout: string; stderr: string }> {
const env = gitCommandEnv(process.env);
const child = spawn("git", gitArgs(args), { cwd, stdio: ["ignore", "pipe", "pipe"], env });
let stdout = "";
@@ -596,20 +643,43 @@ async function git(args: string[], cwd: string, options: { allowFailure?: boolea
clearTimeout(timeout);
if (result.code !== 0 && !options.allowFailure) {
const failureKind = timedOut ? "backend-timeout" : "infra-failed";
throw new AgentRunError(failureKind, `git ${args[0] ?? "command"} ${timedOut ? `timed out after ${timeoutMs}ms` : `failed with code ${result.code}`}`, {
const stderrCategory = timedOut ? "operation-timeout" : classifyGitStderr(stderr);
throw new AgentRunError(failureKind, `git ${args[0] ?? "command"} ${timedOut ? `timed out after ${timeoutMs}ms` : `failed with code ${result.code} (${stderrCategory})`}`, {
httpStatus: timedOut ? 504 : 502,
details: {
operation: options.operation ?? args[0] ?? "git",
repoUrl: options.repoUrl ?? null,
fetchRepoUrl: options.fetchRepoUrl ?? null,
stage: options.operation === "fetch" ? "git-fetch" : `git-${options.operation ?? args[0] ?? "command"}`,
source: {
bundle: options.sourceContext ?? null,
sourceRef: options.sourceRef ? { present: true, kind: options.sourceRef.kind, value: options.sourceRef.value, valuesPrinted: false } : { present: false, kind: null, value: null, valuesPrinted: false },
credentialRefPresent: options.credentialRefPresent === true,
valuesPrinted: false,
},
remote: {
declared: gitRemoteSummary(options.repoUrl),
fetch: gitRemoteSummary(options.fetchRepoUrl),
mirrorUsed: options.mirrorUsed === true,
valuesPrinted: false,
},
stderr: {
category: stderrCategory,
firstLineCategory: classifyGitStderr(firstNonEmptyLine(stderr)),
bytes: Buffer.byteLength(stderr, "utf8"),
lines: nonEmptyLineCount(stderr),
truncated: stderr.length > 4000,
valuesPrinted: false,
},
stdout: {
bytes: Buffer.byteLength(stdout, "utf8"),
truncated: stdout.length > 1000,
valuesPrinted: false,
},
timeoutMs,
elapsedMs: Date.now() - startedAt,
protocol: defaultGitHttpVersion,
terminalPrompt: false,
credentialHelper: "gh-auth-setup-git",
proxyDecision: proxyDecisionForUrl(options.fetchRepoUrl ?? options.repoUrl ?? ""),
stderr: redactText(stderr.slice(-4000)),
stdout: redactText(stdout.slice(-1000)),
signal: timedOut ? "SIGTERM" : result.signal,
valuesPrinted: false,
},
@@ -618,6 +688,30 @@ async function git(args: string[], cwd: string, options: { allowFailure?: boolea
return { stdout, stderr };
}
function gitRemoteSummary(value: string | undefined): JsonRecord | null {
const remote = optionalNonEmpty(value);
if (!remote) return null;
const scp = /^(?:[^@\s]+@)?([^:/\s]+):/u.exec(remote);
let scheme = scp ? "ssh" : "file";
let host: string | null = scp?.[1] ?? null;
try {
const parsed = new URL(remote);
scheme = parsed.protocol.replace(/:$/u, "") || scheme;
host = parsed.hostname || null;
} catch {
// Local paths and SCP-style SSH remotes are summarized without parsing their path.
}
return { scheme, host, fingerprint: sha256Text(remote), valuesPrinted: false };
}
function firstNonEmptyLine(value: string): string {
return value.split(/\r?\n/u).find((line) => line.trim().length > 0)?.trim() ?? "";
}
function nonEmptyLineCount(value: string): number {
return value.split(/\r?\n/u).filter((line) => line.trim().length > 0).length;
}
function gitArgs(args: string[]): string[] {
return [
"-c", `http.version=${defaultGitHttpVersion}`,
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { execFile as execFileCallback } from "node:child_process";
import { createHash } from "node:crypto";
import { promisify } from "node:util";
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
@@ -178,9 +179,17 @@ async function assertSessionProfileSwitchAllowed(client: ManagerClient, context:
async function assertResourceBundleFailure(client: ManagerClient, context: SelfTestContext, managerUrl: string): Promise<void> {
const repo = await createLocalGitRepo(context);
const missingCommit = "0000000000000000000000000000000000000000";
const run = await client.post("/api/v1/runs", {
...runPayload(context, "codex", "selftest-bad-bundle-session"),
resourceBundleRef: { kind: "gitbundle", repoUrl: repo.repoUrl, commitId: "0000000000000000000000000000000000000000", bundles: [{ name: "tools", subpath: "tools", targetPath: "tools" }], submodules: false, lfs: false },
resourceBundleRef: {
kind: "gitbundle",
repoUrl: repo.repoUrl,
commitId: repo.commitId,
bundles: [{ name: "missing-tools", repoUrl: repo.repoUrl, commitId: missingCommit, subpath: "tools", targetPath: "tools" }],
submodules: false,
lfs: false,
},
}) as { id: string };
const command = await client.post(`/api/v1/runs/${run.id}/commands`, { type: "turn", payload: { prompt: "bad bundle" }, idempotencyKey: "selftest-bad-bundle" }) as { id: string };
const result = await runOnce({ managerUrl, runId: run.id, commandId: command.id, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "bad-bundle-workspaces") }, oneShot: true }) as JsonRecord;
@@ -189,6 +198,33 @@ async function assertResourceBundleFailure(client: ManagerClient, context: SelfT
const envelope = await client.get(`/api/v1/runs/${run.id}/commands/${command.id}/result`) as JsonRecord;
assert.equal(envelope.completed, false);
assert.equal(envelope.failureKind, "infra-failed");
const failureDetails = envelope.failureDetails as JsonRecord;
const source = failureDetails.source as JsonRecord;
const bundle = source.bundle as JsonRecord;
const sourceRef = source.sourceRef as JsonRecord;
const remote = failureDetails.remote as JsonRecord;
const stderr = failureDetails.stderr as JsonRecord;
const stdout = failureDetails.stdout as JsonRecord;
assert.equal(failureDetails.stage, "git-fetch");
assert.deepEqual(bundle, { kind: "bundle", index: 0, name: "missing-tools" });
assert.equal(sourceRef.present, true);
assert.equal(sourceRef.kind, "commit");
assert.equal(sourceRef.value, missingCommit);
assert.equal(source.credentialRefPresent, false);
assert.equal(remote.mirrorUsed, false);
assert.equal((remote.declared as JsonRecord).scheme, "file");
const expectedRemoteFingerprint = createHash("sha256").update(repo.repoUrl, "utf8").digest("hex");
assert.equal((remote.declared as JsonRecord).fingerprint, expectedRemoteFingerprint);
assert.equal((remote.fetch as JsonRecord).fingerprint, expectedRemoteFingerprint);
assert.equal(stderr.category, "source-ref-not-found");
assert.equal(stderr.firstLineCategory, "source-ref-not-found");
assert.equal(typeof stderr.bytes, "number");
assert.equal(typeof stderr.lines, "number");
assert.equal(Object.hasOwn(failureDetails, "repoUrl"), false);
assert.equal(Object.hasOwn(stderr, "text"), false);
assert.equal(Object.hasOwn(stdout, "text"), false);
assert.equal(JSON.stringify(failureDetails).includes(repo.repoUrl), false);
assertNoSecretLeak(failureDetails);
const commandRecord = await client.get(`/api/v1/runs/${run.id}/commands/${command.id}`) as { state?: string };
assert.equal(commandRecord.state, "failed");
const runRecord = await client.get(`/api/v1/runs/${run.id}`) as { status?: string; terminalStatus?: string; failureKind?: string };
+5 -2
View File
@@ -246,9 +246,12 @@ process.exit(1);
const blockedDispatched = await client.post(`/api/v1/queue/tasks/${blockedCreated.id}/dispatch`, { attemptId: "attempt_queue_q2_blocked_selftest" }) as QueueDispatchResult;
await client.patch(`/api/v1/commands/${blockedDispatched.command.id}/status`, { terminalStatus: "blocked", failureKind: "required-skill-unavailable", failureMessage: "missing required skill" });
await client.patch(`/api/v1/runs/${blockedDispatched.run.id}/status`, { terminalStatus: "blocked", failureKind: "required-skill-unavailable", failureMessage: "missing required skill" });
const blockedShown = await client.get(`/api/v1/queue/tasks/${blockedCreated.id}`) as QueueTaskRecord;
const blockedShown = await client.get(`/api/v1/queue/tasks/${blockedCreated.id}`) as QueueTaskRecord & { failureKind?: string; failureMessage?: string; supervisor?: JsonRecord };
assert.equal(blockedShown.state, "blocked");
assert.equal(blockedShown.latestAttempt?.state, "blocked");
assert.equal(blockedShown.failureKind, "required-skill-unavailable");
assert.equal(blockedShown.failureMessage, "missing required skill");
assert.equal(blockedShown.supervisor?.failureKind, "required-skill-unavailable");
const cancelCreated = await client.post("/api/v1/queue/tasks", {
tenantId: "unidesk",
@@ -296,7 +299,7 @@ process.exit(1);
assert.ok(JSON.stringify(cancelManifest).includes(cancelDispatched.run.id));
assertNoSecretLeak(dispatched);
assertNoSecretLeak(cancelled);
return { name: "queue-q2-dispatch", tests: ["queue-cli-json-stdin-dry-run", "queue-dispatch-run-command-runner-job", "queue-read-views-refresh-terminal-state", "queue-refresh-from-core-status", "queue-dispatch-no-repeat", "queue-unidesk-ssh-endpoint-auto-env", "queue-blocked-run-state-wins-over-command-failed", "queue-cancel-propagates-to-run-command-session"] };
return { name: "queue-q2-dispatch", tests: ["queue-cli-json-stdin-dry-run", "queue-dispatch-run-command-runner-job", "queue-read-views-refresh-terminal-state", "queue-refresh-from-core-status", "queue-dispatch-no-repeat", "queue-unidesk-ssh-endpoint-auto-env", "queue-blocked-run-state-wins-over-command-failed", "queue-read-projects-terminal-failure", "queue-cancel-propagates-to-run-command-session"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
+5 -2
View File
@@ -6,7 +6,7 @@ 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 { classifyGitStderr, resolveGitBundleFetchSource } from "../../runner/resource-bundle.js";
import { assertNoSecretLeak, loadArtificerImageRef, type SelfTestCase } from "../harness.js";
const selfTest: SelfTestCase = async (context) => {
@@ -79,6 +79,9 @@ const selfTest: SelfTestCase = async (context) => {
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.equal(classifyGitStderr("fatal: repository 'http://mirror.example.test/pikasTech/agent_skills.git/' not found\n"), "repository-not-found");
assert.equal(classifyGitStderr("fatal: couldn't find remote ref missing-branch\n"), "source-ref-not-found");
assert.equal(classifyGitStderr("fatal: Authentication failed for 'https://example.test/repo.git/'\n"), "authentication-failed");
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"]);
@@ -93,7 +96,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-github-url-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", "git-fetch-stderr-classification", "queue-submit-aipod-dry-run", "aipod-cli-help"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}