From 4839bb424cdcd1a50a5bbccbdf88d9d0336897b5 Mon Sep 17 00:00:00 2001 From: AgentRun Codex Date: Tue, 14 Jul 2026 12:59:20 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A3=B0=E6=98=8E=20Artificer=20?= =?UTF-8?q?=E7=A7=81=E6=9C=89=E4=BB=93=20GitHub=20=E5=87=AD=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/aipods/artificer.yaml | 11 +++++ src/selftest/cases/76-aipod-spec.ts | 72 ++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/config/aipods/artificer.yaml b/config/aipods/artificer.yaml index 95b4d7c..3de1768 100644 --- a/config/aipods/artificer.yaml +++ b/config/aipods/artificer.yaml @@ -55,6 +55,17 @@ spec: kind: env envName: GH_TOKEN secretKey: GH_TOKEN + - tool: github + purpose: repository-override-pikainc-pikaoa + secretRef: + namespace: agentrun-v02 + name: agentrun-v02-tool-github-repository-overrides + keys: + - PIKAINC_PIKAOA_GH_TOKEN + projection: + kind: env + envName: UNIDESK_GH_TOKEN_PIKAINC_PIKAOA + secretKey: PIKAINC_PIKAOA_GH_TOKEN - tool: unidesk-ssh purpose: ssh-passthrough secretRef: diff --git a/src/selftest/cases/76-aipod-spec.ts b/src/selftest/cases/76-aipod-spec.ts index e4b2a26..3fad0fa 100644 --- a/src/selftest/cases/76-aipod-spec.ts +++ b/src/selftest/cases/76-aipod-spec.ts @@ -9,6 +9,7 @@ import { MemoryAgentRunStore } from "../../mgr/store.js"; import type { JsonRecord } from "../../common/types.js"; import { validateAipodSpec } from "../../common/aipod-specs.js"; import { validateCreateCommand, validateCreateRun, validatePrimaryWorkspaceContract, validateResourceBundleRef } from "../../common/validation.js"; +import { renderRunnerJobDryRun } from "../../runner/k8s-job.js"; import { classifyGitStderr, resolveGitBundleFetchSource } from "../../runner/resource-bundle.js"; import { assertNoSecretLeak, loadArtificerImageRef, type SelfTestCase } from "../harness.js"; @@ -70,6 +71,19 @@ const selfTest: SelfTestCase = async (context) => { const shownProviderCredential = (shownProviderCredentials.items as JsonRecord[]).find((item) => item.profile === "gpt-pika"); assert.equal(shownProviderCredential?.name, "agentrun-v02-provider-gpt-pika"); assert.equal(shownProviderCredential?.namespace, "agentrun-v02"); + const shownToolCredentials = shownItem.toolCredentials as JsonRecord; + assert.equal(shownToolCredentials.count, 4); + assert.equal(shownToolCredentials.valuesPrinted, false); + const shownRepositoryOverride = (shownToolCredentials.items as JsonRecord[]).find((item) => item.purpose === "repository-override-pikainc-pikaoa") as JsonRecord | undefined; + assert.deepEqual(shownRepositoryOverride, { + tool: "github", + purpose: "repository-override-pikainc-pikaoa", + name: "agentrun-v02-tool-github-repository-overrides", + namespace: "agentrun-v02", + keys: ["PIKAINC_PIKAOA_GH_TOKEN"], + projection: { kind: "env", envName: "UNIDESK_GH_TOKEN_PIKAINC_PIKAOA", secretKey: "PIKAINC_PIKAOA_GH_TOKEN" }, + valuesPrinted: false, + }); const shownImageRef = shownItem.imageRef as JsonRecord; assert.equal(shownImageRef.kind, "env-image-dockerfile"); assert.equal(shownImageRef.repoUrl, "git@github.com:pikasTech/agentrun.git"); @@ -124,6 +138,17 @@ const selfTest: SelfTestCase = async (context) => { assert.deepEqual(disclosedProviderCredential.secretRef, { name: "agentrun-v02-provider-gpt-pika", namespace: "agentrun-v02", keys: ["auth.json", "config.toml"], valuesPrinted: false }); const toolCredentials = secretScope.toolCredentials as JsonRecord[]; assert.equal(toolCredentials.some((item) => item.tool === "github" && ((item.projection as JsonRecord).kind) === "env" && ((item.projection as JsonRecord).envName) === "GH_TOKEN" && ((item.secretRef as JsonRecord).name) === "agentrun-v01-tool-github-pr"), true); + const repositoryOverride = toolCredentials.find((item) => item.tool === "github" && item.purpose === "repository-override-pikainc-pikaoa") as JsonRecord | undefined; + assert.deepEqual(repositoryOverride, { + tool: "github", + purpose: "repository-override-pikainc-pikaoa", + secretRef: { + namespace: "agentrun-v02", + name: "agentrun-v02-tool-github-repository-overrides", + keys: ["PIKAINC_PIKAOA_GH_TOKEN"], + }, + projection: { kind: "env", envName: "UNIDESK_GH_TOKEN_PIKAINC_PIKAOA", secretKey: "PIKAINC_PIKAOA_GH_TOKEN" }, + }); assert.equal(toolCredentials.some((item) => item.tool === "unidesk-ssh" && ((item.projection as JsonRecord).envName) === "UNIDESK_SSH_CLIENT_TOKEN"), true); const githubSshCredential = toolCredentials.find((item) => item.tool === "github" && item.purpose === "github-ssh") as JsonRecord | undefined; assert.equal(((githubSshCredential?.projection as JsonRecord).kind), "volume"); @@ -141,6 +166,42 @@ const selfTest: SelfTestCase = async (context) => { assert.equal((bundle.requiredSkills as JsonRecord[]).some((item) => item.name === "unidesk-gh"), true); assertNoSecretLeak(rendered); + const runnerRun = store.createRun(validateCreateRun({ + tenantId: task.tenantId, + projectId: task.projectId, + providerId: task.providerId, + backendProfile: task.backendProfile, + workspaceRef: task.workspaceRef, + executionPolicy: task.executionPolicy, + resourceBundleRef: task.resourceBundleRef, + traceSink: { kind: "self-test", source: "artificer-private-repository-override" }, + })); + const runnerCommand = store.createCommand(runnerRun.id, validateCreateCommand({ type: "turn", payload: { prompt: "验证私有仓凭据投影" } })); + const runnerJob = renderRunnerJobDryRun({ + run: runnerRun, + commandId: runnerCommand.id, + managerUrl: server.baseUrl, + runnerApiKeySecretRef: { name: "agentrun-v02-api-key", key: "HWLAB_API_KEY" }, + image: "agentrun-selftest:latest", + namespace: "agentrun-v02", + attemptId: "attempt_artificer_private_repository_override", + sourceCommit: "self-test", + }); + const overrideEnv = runnerEnvEntry(runnerJob.manifest as JsonRecord, "UNIDESK_GH_TOKEN_PIKAINC_PIKAOA"); + assert.ok(overrideEnv, "Artificer runner Job must project the repository override credential"); + assert.equal(overrideEnv.value, undefined); + assert.deepEqual(((overrideEnv.valueFrom as JsonRecord).secretKeyRef), { + name: "agentrun-v02-tool-github-repository-overrides", + key: "PIKAINC_PIKAOA_GH_TOKEN", + }); + const defaultGithubEnv = runnerEnvEntry(runnerJob.manifest as JsonRecord, "GH_TOKEN"); + assert.deepEqual(((defaultGithubEnv?.valueFrom as JsonRecord).secretKeyRef), { name: "agentrun-v01-tool-github-pr", key: "GH_TOKEN" }); + const runnerToolCredentialSummary = runnerJob.toolCredentials as JsonRecord; + assert.equal(runnerToolCredentialSummary.count, 4); + assert.equal(runnerToolCredentialSummary.valuesPrinted, false); + assert.equal((runnerToolCredentialSummary.items as JsonRecord[]).every((item) => item.valuesPrinted === false), true); + assertNoSecretLeak(runnerJob); + const modelOverride = await client.post("/api/v1/aipod-specs/Artificer/render", { prompt: "override model", model: "gpt-5.4", reasoningEffort: "high" }) as JsonRecord; const modelOverrideTask = modelOverride.queueTask as JsonRecord; const modelOverridePayload = modelOverrideTask.payload as JsonRecord; @@ -292,7 +353,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-v02-runtime-authority", "aipod-spec-gpt-pika-secret-namespace", "aipod-spec-model-defaults", "aipod-spec-model-reasoning-overrides", "aipod-spec-model-override-keeps-provider", "aipod-spec-control-override-fail-closed", "aipod-spec-payload-control-bypass-fail-closed", "aipod-spec-missing-gpt-pika-credential", "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-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-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-v02-runtime-authority", "aipod-spec-gpt-pika-secret-namespace", "aipod-spec-model-defaults", "aipod-spec-model-reasoning-overrides", "aipod-spec-model-override-keeps-provider", "aipod-spec-control-override-fail-closed", "aipod-spec-payload-control-bypass-fail-closed", "aipod-spec-missing-gpt-pika-credential", "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-cancelled-durable-identity", "session-send-aipod-boundary-conflict-fail-closed", "aipod-cli-help"] }; } finally { await new Promise((resolve) => server.server.close(() => resolve())); } @@ -311,6 +372,15 @@ console.log(JSON.stringify({ name: spec.metadata.name, hasBunGlobal: typeof glob return JSON.parse(stdout) as JsonRecord; } +function runnerEnvEntry(manifest: JsonRecord, name: string): JsonRecord | undefined { + const spec = manifest.spec as JsonRecord; + const template = spec.template as JsonRecord; + const podSpec = template.spec as JsonRecord; + const containers = podSpec.containers as JsonRecord[]; + const runner = containers[0] as JsonRecord; + return (runner.env as JsonRecord[]).find((item) => item.name === name); +} + async function runCliJson(context: { root: string }, managerUrl: string, args: string[]): Promise { const proc = spawn(process.execPath, [`${context.root}/scripts/agentrun-cli.ts`, "--manager-url", managerUrl, ...args], { stdio: ["ignore", "pipe", "pipe"] }); const [stdout, stderr, code] = await Promise.all([readStream(proc.stdout), readStream(proc.stderr), new Promise((resolve) => proc.on("close", resolve))]);