fix: 为 Artificer runner 注入 Git identity
This commit is contained in:
@@ -31,6 +31,9 @@ spec:
|
||||
approval: never
|
||||
timeoutMs: 1800000
|
||||
network: enabled
|
||||
gitIdentity:
|
||||
name: AgentRun Codex
|
||||
email: agentrun-codex@users.noreply.github.com
|
||||
secretScope:
|
||||
allowCredentialEcho: false
|
||||
providerCredentials:
|
||||
|
||||
@@ -192,6 +192,9 @@ export function summarizeAipodSpecRecord(record: AipodSpecRecord): JsonRecord {
|
||||
providerId: spec.providerId ?? "G14",
|
||||
providerCredentials: summarizeProviderCredentials(spec.executionPolicy),
|
||||
toolCredentials: summarizeToolCredentials(spec.executionPolicy),
|
||||
gitIdentity: spec.executionPolicy.gitIdentity
|
||||
? { ...spec.executionPolicy.gitIdentity, source: `${record.source}#spec.executionPolicy.gitIdentity`, valuesPrinted: true }
|
||||
: null,
|
||||
resourceBundleRef: summarizeResourceBundle(spec.resourceBundleRef),
|
||||
dispatchDefaults: summarizeKeys(spec.dispatchDefaults),
|
||||
createdAt: record.createdAt,
|
||||
|
||||
@@ -182,6 +182,10 @@ export interface ExecutionPolicy extends JsonRecord {
|
||||
approval: string;
|
||||
timeoutMs: number;
|
||||
network: string;
|
||||
gitIdentity?: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
secretScope: {
|
||||
providerCredentials?: Array<{
|
||||
profile: BackendProfile | string;
|
||||
|
||||
@@ -262,15 +262,30 @@ export function validateExecutionPolicy(record: JsonRecord): ExecutionPolicy {
|
||||
const secretScopeResult: ExecutionPolicy["secretScope"] = { allowCredentialEcho: false };
|
||||
if (providerCredentials.length > 0) secretScopeResult.providerCredentials = providerCredentials as NonNullable<ExecutionPolicy["secretScope"]["providerCredentials"]>;
|
||||
if (toolCredentials.length > 0) secretScopeResult.toolCredentials = toolCredentials;
|
||||
const gitIdentity = record.gitIdentity === undefined ? undefined : validateGitIdentity(record.gitIdentity);
|
||||
return {
|
||||
sandbox: requiredString(record, "sandbox"),
|
||||
approval: requiredString(record, "approval"),
|
||||
timeoutMs: timeout,
|
||||
network: requiredString(record, "network"),
|
||||
...(gitIdentity ? { gitIdentity } : {}),
|
||||
secretScope: secretScopeResult,
|
||||
};
|
||||
}
|
||||
|
||||
function validateGitIdentity(value: unknown): NonNullable<ExecutionPolicy["gitIdentity"]> {
|
||||
const record = asRecord(value, "executionPolicy.gitIdentity");
|
||||
const name = requiredString(record, "name");
|
||||
const email = requiredString(record, "email");
|
||||
if (/[\r\n]/u.test(name)) {
|
||||
throw new AgentRunError("schema-invalid", "executionPolicy.gitIdentity.name must be a single line", { httpStatus: 400 });
|
||||
}
|
||||
if (/[\r\n<>]/u.test(email) || !/^[^\s@]+@[^\s@]+$/u.test(email)) {
|
||||
throw new AgentRunError("schema-invalid", "executionPolicy.gitIdentity.email must be a valid single-line email address", { httpStatus: 400 });
|
||||
}
|
||||
return { name, email };
|
||||
}
|
||||
|
||||
function validateToolCredentials(value: unknown): NonNullable<ExecutionPolicy["secretScope"]["toolCredentials"]> {
|
||||
if (value === undefined) return [];
|
||||
if (!Array.isArray(value)) throw new AgentRunError("schema-invalid", "toolCredentials must be an array", { httpStatus: 400 });
|
||||
|
||||
@@ -315,6 +315,9 @@ async function createKubernetesRunnerJobUnderFence(options: { store: AgentRunSto
|
||||
toolCredentials: summarizeToolCredentials(render.toolCredentials, render.namespace),
|
||||
gitTransport: gitTransportSummary({ scope: "runner" }),
|
||||
backendGitTransport: hasGithubSshCredential(run.executionPolicy) ? gitTransportSummary({ sshOnly: true, scope: "backend-command" }) : null,
|
||||
gitIdentity: run.executionPolicy.gitIdentity
|
||||
? { ...run.executionPolicy.gitIdentity, source: "executionPolicy.gitIdentity", author: true, committer: true, valuesPrinted: true }
|
||||
: null,
|
||||
transientEnv: summarizeTransientEnv(transientEnv),
|
||||
transientEnvSecret: transientEnvSecretResponse,
|
||||
sessionPvc: sessionPvcSummary,
|
||||
|
||||
@@ -1279,12 +1279,14 @@ function inheritSessionRunResources(runBody: JsonRecord, resourceRun: RunRecord
|
||||
const toolCredentials = Array.isArray(secretScope.toolCredentials) && secretScope.toolCredentials.length > 0
|
||||
? secretScope.toolCredentials
|
||||
: sourceToolCredentials;
|
||||
const gitIdentity = asJsonRecord(executionPolicy.gitIdentity) ?? resourceRun.executionPolicy.gitIdentity;
|
||||
return {
|
||||
...runBody,
|
||||
workspaceRef: resourceRun.workspaceRef,
|
||||
resourceBundleRef: resourceRun.resourceBundleRef,
|
||||
executionPolicy: {
|
||||
...executionPolicy,
|
||||
...(gitIdentity ? { gitIdentity } : {}),
|
||||
secretScope: {
|
||||
...secretScope,
|
||||
...(toolCredentials && toolCredentials.length > 0 ? { toolCredentials } : {}),
|
||||
|
||||
@@ -154,6 +154,7 @@ export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonReco
|
||||
resourceCredential: summarizeResourceCredential(render.resourceCredential, render.namespace),
|
||||
gitTransport: gitTransportSummary({ scope: "runner" }),
|
||||
backendGitTransport: hasGithubSshCredential(render.toolCredentials) ? gitTransportSummary({ sshOnly: true, scope: "backend-command" }) : null,
|
||||
gitIdentity: summarizeGitIdentity(options.run.executionPolicy),
|
||||
transientEnv: summarizeTransientEnv(options.transientEnv ?? []),
|
||||
workReady: staticWorkReadyCapabilitySummary(),
|
||||
retention: {
|
||||
@@ -308,6 +309,7 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
|
||||
{ name: "AGENTRUN_RUNNER_POLL_INTERVAL_MS", value: "250" },
|
||||
{ name: "HOME", value: "/home/agentrun" },
|
||||
{ name: "CODEX_HOME", value: codexHome },
|
||||
...gitIdentityEnvVars(options.run.executionPolicy),
|
||||
{ name: "AGENTRUN_CODEX_HOME_STORAGE_KIND", value: context.sessionPvc ? "session-pvc" : "runner-home" },
|
||||
...runnerOtelEnvVars(process.env),
|
||||
...runnerKafkaEnvVars(process.env),
|
||||
@@ -334,6 +336,23 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
|
||||
return env;
|
||||
}
|
||||
|
||||
function gitIdentityEnvVars(policy: ExecutionPolicy): Array<{ name: string; value: string }> {
|
||||
const identity = policy.gitIdentity;
|
||||
if (!identity) return [];
|
||||
return [
|
||||
{ name: "GIT_AUTHOR_NAME", value: identity.name },
|
||||
{ name: "GIT_AUTHOR_EMAIL", value: identity.email },
|
||||
{ name: "GIT_COMMITTER_NAME", value: identity.name },
|
||||
{ name: "GIT_COMMITTER_EMAIL", value: identity.email },
|
||||
];
|
||||
}
|
||||
|
||||
function summarizeGitIdentity(policy: ExecutionPolicy): JsonRecord | null {
|
||||
const identity = policy.gitIdentity;
|
||||
if (!identity) return null;
|
||||
return { ...identity, source: "executionPolicy.gitIdentity", author: true, committer: true, valuesPrinted: true };
|
||||
}
|
||||
|
||||
function normalizeRunnerIdleTimeoutMs(value: number | undefined): number {
|
||||
if (value === undefined) return defaultRunnerIdleTimeoutMs;
|
||||
if (!Number.isInteger(value) || value <= 0) throw new Error("runnerIdleTimeoutMs must be a positive integer");
|
||||
|
||||
@@ -66,6 +66,26 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_API_KEY"]);
|
||||
assertNoSecretLeak(rendered);
|
||||
|
||||
const gitIdentityRendered = renderRunnerJobDryRun({
|
||||
run: {
|
||||
...baseRun,
|
||||
executionPolicy: {
|
||||
...baseRun.executionPolicy,
|
||||
gitIdentity: { name: "AgentRun Codex", email: "agentrun-codex@users.noreply.github.com" },
|
||||
},
|
||||
},
|
||||
commandId: item.commandId,
|
||||
managerUrl: server.baseUrl,
|
||||
runnerApiKeySecretRef,
|
||||
image: "127.0.0.1:5000/agentrun/agentrun-mgr@sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
});
|
||||
const gitIdentityManifest = gitIdentityRendered.manifest as JsonRecord;
|
||||
assert.equal(runnerEnvValue(gitIdentityManifest, "GIT_AUTHOR_NAME"), "AgentRun Codex");
|
||||
assert.equal(runnerEnvValue(gitIdentityManifest, "GIT_AUTHOR_EMAIL"), "agentrun-codex@users.noreply.github.com");
|
||||
assert.equal(runnerEnvValue(gitIdentityManifest, "GIT_COMMITTER_NAME"), "AgentRun Codex");
|
||||
assert.equal(runnerEnvValue(gitIdentityManifest, "GIT_COMMITTER_EMAIL"), "agentrun-codex@users.noreply.github.com");
|
||||
assert.deepEqual(gitIdentityRendered.gitIdentity, { name: "AgentRun Codex", email: "agentrun-codex@users.noreply.github.com", source: "executionPolicy.gitIdentity", author: true, committer: true, valuesPrinted: true });
|
||||
|
||||
const resourceCredentialOptions: Parameters<typeof renderRunnerJobDryRun>[0] = {
|
||||
run: {
|
||||
...baseRun,
|
||||
|
||||
@@ -313,6 +313,7 @@ async function assertSessionFollowUpInheritsAipodResources(kubectlCommand: strin
|
||||
assert.deepEqual(run.workspaceRef, initial.workspaceRef);
|
||||
assert.deepEqual(run.resourceBundleRef, initial.resourceBundleRef);
|
||||
assert.deepEqual(run.executionPolicy.secretScope.toolCredentials, initial.executionPolicy.secretScope.toolCredentials);
|
||||
assert.deepEqual(run.executionPolicy.gitIdentity, initial.executionPolicy.gitIdentity);
|
||||
assert.deepEqual(run.executionPolicy.secretScope.providerCredentials, runInput(sessionId).executionPolicy.secretScope.providerCredentials);
|
||||
} finally {
|
||||
await closeServer(server.server);
|
||||
@@ -411,6 +412,7 @@ function artificerRunInput(sessionId: string): CreateRunInput {
|
||||
},
|
||||
executionPolicy: {
|
||||
...input.executionPolicy,
|
||||
gitIdentity: { name: "AgentRun Codex", email: "agentrun-codex@users.noreply.github.com" },
|
||||
secretScope: {
|
||||
...input.executionPolicy.secretScope,
|
||||
toolCredentials: [
|
||||
|
||||
@@ -51,6 +51,12 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.equal(shownItem.lane, "v0.2");
|
||||
assert.equal(((shownItem.model as JsonRecord).model), "gpt-5.6-terra");
|
||||
assert.equal(((shownItem.model as JsonRecord).reasoningEffort), "xhigh");
|
||||
assert.deepEqual(shownItem.gitIdentity, {
|
||||
name: "AgentRun Codex",
|
||||
email: "agentrun-codex@users.noreply.github.com",
|
||||
source: `${artificerSpecFile}#spec.executionPolicy.gitIdentity`,
|
||||
valuesPrinted: true,
|
||||
});
|
||||
const shownModelDefaults = shownItem.modelDefaults as JsonRecord;
|
||||
assert.equal(shownModelDefaults.modelSource, "aipod-spec-default");
|
||||
assert.equal(shownModelDefaults.reasoningEffortSource, "aipod-spec-default");
|
||||
@@ -98,6 +104,7 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assert.equal(modelResolution.backendProfileSource, "aipod-spec");
|
||||
assert.equal(modelResolution.valuesPrinted, false);
|
||||
const policy = task.executionPolicy as JsonRecord;
|
||||
assert.deepEqual(policy.gitIdentity, { name: "AgentRun Codex", email: "agentrun-codex@users.noreply.github.com" });
|
||||
const secretScope = policy.secretScope as JsonRecord;
|
||||
const providerCredentials = secretScope.providerCredentials as JsonRecord[];
|
||||
const providerCredential = providerCredentials.find((item) => item.profile === "gpt-pika") as JsonRecord | undefined;
|
||||
@@ -166,6 +173,9 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
const payloadDefaultsBypassDocument = structuredClone(originalSpecDocument);
|
||||
((payloadDefaultsBypassDocument.spec as JsonRecord).payloadDefaults as JsonRecord).modelConfig = { model: "bypass" };
|
||||
assert.throws(() => validateAipodSpec(payloadDefaultsBypassDocument, "selftest-payload-defaults-bypass"), /cannot set Aipod control fields: modelConfig/u);
|
||||
const invalidGitIdentityDocument = structuredClone(originalSpecDocument);
|
||||
(((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");
|
||||
|
||||
Reference in New Issue
Block a user