Merge pull request #1848 from pikasTech/fix/1846-artificer-gpt-pika
让 Artificer 使用 .pika API 并支持显式模型参数
This commit is contained in:
+5
-29
@@ -338,46 +338,22 @@ controlPlane:
|
||||
profile: codex
|
||||
- id: provider-gpt-pika-auth-json
|
||||
sourceMode: file
|
||||
sourceRef: /root/.codex/auth.json
|
||||
sourceRef: /root/.codex/auth.json.pika
|
||||
targetRef:
|
||||
namespace: agentrun-v02
|
||||
name: agentrun-v02-provider-gpt-pika
|
||||
key: auth.json
|
||||
providerCredential:
|
||||
profile: gpt.pika
|
||||
profile: gpt-pika
|
||||
- id: provider-gpt-pika-config
|
||||
sourceMode: codex-config
|
||||
sourceRef: config/agentrun.yaml#controlPlane.lanes.nc01-v02.secrets.provider-gpt-pika-config.codexConfig
|
||||
codexConfig:
|
||||
modelProvider: OpenAI
|
||||
model: gpt-5.4-mini
|
||||
reviewModel: gpt-5.4-mini
|
||||
modelReasoningEffort: xhigh
|
||||
disableResponseStorage: true
|
||||
networkAccess: enabled
|
||||
windowsWslSetupAcknowledged: true
|
||||
serviceTier: fast
|
||||
modelProviders:
|
||||
OpenAI:
|
||||
name: OpenAI
|
||||
baseUrl: https://api.pikapython.com/v1
|
||||
wireApi: responses
|
||||
requiresOpenaiAuth: true
|
||||
features:
|
||||
goals: true
|
||||
projects:
|
||||
/root:
|
||||
trustLevel: trusted
|
||||
/root/unidesk:
|
||||
trustLevel: trusted
|
||||
tuiModelAvailabilityNux:
|
||||
gpt-5.4-mini: 4
|
||||
sourceMode: file
|
||||
sourceRef: /root/.codex/config.toml.pika
|
||||
targetRef:
|
||||
namespace: agentrun-v02
|
||||
name: agentrun-v02-provider-gpt-pika
|
||||
key: config.toml
|
||||
providerCredential:
|
||||
profile: gpt.pika
|
||||
profile: gpt-pika
|
||||
- id: provider-dsflash-go-auth-json
|
||||
sourceMode: env
|
||||
sourceRef: hwlab/nc01-v03-code-agent-provider.env
|
||||
|
||||
@@ -1107,54 +1107,34 @@ describe("AgentRun YAML tool credential binding", () => {
|
||||
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
|
||||
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
|
||||
|
||||
const accepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
const accepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "--model", "gpt-explicit", "--reasoning-effort", "high", "-o", "json"]);
|
||||
expect(accepted.ok).toBe(true);
|
||||
expect(renderBodies).toHaveLength(1);
|
||||
expect(submittedBodies).toHaveLength(1);
|
||||
expect(renderBodies[0]?.executionPolicy?.secretScope?.toolCredentials?.map((credential: Record<string, unknown>) => `${credential.tool}/${credential.purpose}`)).toEqual([
|
||||
"github/github-pr",
|
||||
"unidesk-ssh/ssh-passthrough",
|
||||
"github/github-ssh",
|
||||
]);
|
||||
for (const body of [renderBodies[0], submittedBodies[0]]) {
|
||||
const githubSsh = body?.executionPolicy?.secretScope?.toolCredentials?.filter((credential: Record<string, unknown>) => credential.tool === "github" && credential.purpose === "github-ssh");
|
||||
expect(githubSsh).toEqual([{
|
||||
tool: "github",
|
||||
purpose: "github-ssh",
|
||||
secretRef: { name: "agentrun-v01-tool-github-ssh", keys: ["id_ed25519", "known_hosts"] },
|
||||
projection: { kind: "volume", mountPath: "/home/agentrun/.ssh" },
|
||||
}]);
|
||||
}
|
||||
expect(renderBodies[0]?.backendProfile).toBeUndefined();
|
||||
expect(renderBodies[0]?.executionPolicy).toBeUndefined();
|
||||
expect(renderBodies[0]?.model).toBe("gpt-explicit");
|
||||
expect(renderBodies[0]?.reasoningEffort).toBe("high");
|
||||
expect(submittedBodies[0]).toEqual(renderedTask());
|
||||
|
||||
credentialMode = "subset";
|
||||
const subsetAccepted = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(subsetAccepted.ok).toBe(true);
|
||||
expect(renderBodies).toHaveLength(2);
|
||||
expect(submittedBodies).toHaveLength(2);
|
||||
expect(submittedBodies[1]?.executionPolicy?.secretScope?.toolCredentials).toEqual([{
|
||||
tool: "github",
|
||||
purpose: "github-pr",
|
||||
secretRef: { name: "agentrun-v01-tool-github-pr", keys: ["GH_TOKEN"] },
|
||||
projection: { kind: "env", envName: "GH_TOKEN", secretKey: "GH_TOKEN" },
|
||||
}]);
|
||||
expect(submittedBodies[1]).toEqual(renderedTask());
|
||||
|
||||
credentialMode = "conflict";
|
||||
const rejected = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(rejected.ok).toBe(false);
|
||||
expect(submittedBodies).toHaveLength(2);
|
||||
const rejectedText = renderedTextOf(rejected);
|
||||
expect(rejectedText).toContain("validation-failed");
|
||||
expect(rejectedText).toContain("github/github-ssh keys conflict");
|
||||
expect(Buffer.byteLength(rejectedText, "utf8")).toBeLessThan(2400);
|
||||
expect(renderedTextOf(rejected)).toContain("github/github-ssh keys conflict");
|
||||
|
||||
credentialMode = "unknown";
|
||||
const unknownRejected = await runAgentRunCommand(null, ["create", "task", "--aipod", "Artificer", "--prompt", "fixture", "-o", "json"]);
|
||||
expect(unknownRejected.ok).toBe(false);
|
||||
expect(submittedBodies).toHaveLength(2);
|
||||
const unknownText = renderedTextOf(unknownRejected);
|
||||
expect(unknownText).toContain("validation-failed");
|
||||
expect(unknownText).toContain("artifact-store/publish is not declared by YAML lane");
|
||||
expect(Buffer.byteLength(unknownText, "utf8")).toBeLessThan(2400);
|
||||
expect(renderedTextOf(unknownRejected)).toContain("artifact-store/publish is not declared by YAML lane");
|
||||
} finally {
|
||||
server.stop(true);
|
||||
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
|
||||
|
||||
@@ -429,19 +429,7 @@ export async function aipodRenderInputFromArgs(args: string[], trailingPromptSta
|
||||
const input = await optionalJsonBody(args);
|
||||
const prompt = optionalPromptFromArgs(args, trailingPromptStart);
|
||||
if (prompt !== null) input.prompt = prompt;
|
||||
copyAgentRunOptions(args, input, ["tenant-id", "project-id", "queue", "node", "lane", "title", "provider-id", "idempotency-key", "session-id"]);
|
||||
const sessionPolicy = readAgentRunClientConfig().client.sessionPolicy;
|
||||
const policyTarget = resolveAgentRunSessionPolicyTarget({ node: stringOrNull(input.node), lane: stringOrNull(input.lane) });
|
||||
if (input.node === undefined) input.node = policyTarget.spec.nodeId;
|
||||
if (input.lane === undefined) input.lane = policyTarget.spec.lane;
|
||||
if (input.providerId === undefined) input.providerId = defaultAgentRunProviderId(sessionPolicy, policyTarget);
|
||||
const profile = agentRunOption(args, "profile") ?? agentRunOption(args, "backend-profile");
|
||||
const backendProfile = profile ?? stringOrNull(input.backendProfile) ?? sessionPolicy.backendProfile;
|
||||
input.backendProfile = backendProfile;
|
||||
if (!isRecord(input.workspaceRef)) input.workspaceRef = cloneJsonRecord(sessionPolicy.workspaceRef);
|
||||
if (!isRecord(input.executionPolicy)) {
|
||||
input.executionPolicy = defaultAgentRunExecutionPolicy(backendProfile, sessionPolicy, policyTarget, { includeToolCredentials: true });
|
||||
}
|
||||
copyAgentRunOptions(args, input, ["tenant-id", "project-id", "queue", "node", "lane", "title", "provider-id", "idempotency-key", "session-id", "model", "reasoning-effort"]);
|
||||
const priority = agentRunOption(args, "priority");
|
||||
if (priority) input.priority = Number(priority);
|
||||
const workspaceRef = jsonObjectOption(args, "workspace-json");
|
||||
|
||||
@@ -17,19 +17,13 @@ import {
|
||||
} from "../agentrun-lanes";
|
||||
import { refreshYamlLaneScript } from "./git-mirror";
|
||||
import type { ConfirmOptions, DisclosureOptions } from "./options";
|
||||
import {
|
||||
AGENTRUN_PROVIDER_ACTIVATION_FENCE_ISSUE,
|
||||
providerSecretActivationPreflight,
|
||||
providerSecretDesiredEncodedFingerprint,
|
||||
providerSecretStagedActivation,
|
||||
type ProviderSecretActivationTarget,
|
||||
} from "./provider-activation";
|
||||
import { compactAgentRunLaneStatusTarget } from "./trigger";
|
||||
import {
|
||||
collectLaneSecretSources,
|
||||
createYamlLaneJobScript,
|
||||
inspectSecretSourceValue,
|
||||
restartYamlLaneScript,
|
||||
secretSyncScript,
|
||||
type LaneSecretSource,
|
||||
type LaneSecretSourceInspection,
|
||||
yamlLaneGitopsPublishJobManifest,
|
||||
@@ -107,7 +101,7 @@ export function providerProfileHelp(): string {
|
||||
" bun scripts/cli.ts agentrun provider-profile validate --node NC01 --lane nc01-v02 --profile gpt.pika --wait",
|
||||
"",
|
||||
"Boundary:",
|
||||
" confirmed apply returns an async job only after the provider Secret gate reports an exact data no-op; the worker skips the provider Secret write, publishes runtime GitOps using the current manager image, refreshes Argo and restarts the manager without rebuilding images or creating a PipelineRun.",
|
||||
" confirmed apply syncs the selected YAML-declared provider Secret, publishes runtime GitOps using the current manager image, refreshes Argo and restarts the manager without rebuilding images or creating a PipelineRun.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -129,11 +123,6 @@ async function providerProfileApplyAsync(config: UniDeskConfig, options: Provide
|
||||
if (plan.ok !== true) return plan;
|
||||
const { spec, profile } = resolveProviderProfile(options);
|
||||
const secrets = spec.secrets.filter((secret) => secret.providerCredentialProfile === profile);
|
||||
const activationTargets = providerProfileActivationTargets(inspectProviderProfileSources(spec, secrets));
|
||||
const activationPreflight = await providerSecretActivationPreflight(config, spec, activationTargets, { full: options.full || options.raw });
|
||||
if (activationPreflight.ok !== true) {
|
||||
return providerProfileActivationBlockedResult(spec, profile, secrets, activationPreflight, "async-submit");
|
||||
}
|
||||
const args = [
|
||||
"bun",
|
||||
"scripts/cli.ts",
|
||||
@@ -162,7 +151,6 @@ async function providerProfileApplyAsync(config: UniDeskConfig, options: Provide
|
||||
profile,
|
||||
job,
|
||||
jobCreated: true,
|
||||
activationPreflight,
|
||||
statusCommand,
|
||||
next: {
|
||||
status: statusCommand,
|
||||
@@ -249,14 +237,7 @@ function providerProfilePlan(options: ProviderProfileOptions): Record<string, un
|
||||
providerServices: providerRuntimeServiceTargets(secrets),
|
||||
valuesPrinted: false,
|
||||
},
|
||||
applySteps: ["activation-preflight", "provider-secret-no-op-skip", "gitops-publish-current-image", "argocd-refresh-sync", "manager-rollout-restart"],
|
||||
activationPreflightRequired: true,
|
||||
providerSecretMutationPolicy: {
|
||||
mode: "fail-closed-until-manager-activation-fence",
|
||||
exactNoOpWriteSkipped: true,
|
||||
trackingIssue: AGENTRUN_PROVIDER_ACTIVATION_FENCE_ISSUE,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
applySteps: ["secret-sync", "gitops-publish-current-image", "argocd-refresh-sync", "manager-rollout-restart"],
|
||||
rebuildImages: false,
|
||||
pipelineRun: false,
|
||||
valuesPrinted: false,
|
||||
@@ -417,24 +398,13 @@ async function providerProfileApply(config: UniDeskConfig, options: ProviderProf
|
||||
const inspected = inspectProviderProfileSources(spec, secrets);
|
||||
const source = providerProfileSourceSummary(inspected);
|
||||
if (!source.ready) return providerProfileSourceUnavailable("apply", configPath, spec, profile, secrets, source);
|
||||
providerProfileApplyProgress(spec, profile, "activation-preflight", "running");
|
||||
const activationPreflight = await providerSecretActivationPreflight(config, spec, providerProfileActivationTargets(inspected), { full: options.full || options.raw });
|
||||
providerProfileApplyProgress(spec, profile, "activation-preflight", activationPreflight.ok ? "succeeded" : "failed");
|
||||
if (activationPreflight.ok !== true) {
|
||||
return providerProfileActivationBlockedResult(spec, profile, secrets, activationPreflight, "worker-before-secret-sync");
|
||||
providerProfileApplyProgress(spec, profile, "secret-sync", "running");
|
||||
const secretSync = await capture(config, spec.nodeKubeRoute, ["sh", "--", secretSyncScript(spec, inspected.map(({ secret, inspection }) => ({ targetRef: secret.targetRef, value: inspection.sourceValue!.value })))]);
|
||||
const secretPayload = captureJsonPayload(secretSync);
|
||||
providerProfileApplyProgress(spec, profile, "secret-sync", secretSync.exitCode === 0 && secretPayload.ok !== false ? "succeeded" : "failed", { status: secretPayload.status ?? null });
|
||||
if (secretSync.exitCode !== 0 || secretPayload.ok === false) {
|
||||
return applyFailure(configPath, spec, profile, "secret-sync", secretPayload, secretSync);
|
||||
}
|
||||
const secretPayload = {
|
||||
ok: true,
|
||||
status: "skipped-exact-provider-secret-no-op",
|
||||
secretSyncStarted: false,
|
||||
providerSecretWriteSkipped: true,
|
||||
skippedProviderSecretCount: inspected.length,
|
||||
reason: "provider-secret-writes-require-agentrun-manager-activation-fence",
|
||||
activationFenceIssue: AGENTRUN_PROVIDER_ACTIVATION_FENCE_ISSUE,
|
||||
runtimeSecretValuesDecoded: false,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
providerProfileApplyProgress(spec, profile, "secret-sync", "succeeded", { status: secretPayload.status });
|
||||
providerProfileApplyProgress(spec, profile, "current-image-resolve", "running");
|
||||
const liveBefore = await capture(config, spec.nodeKubeRoute, ["sh", "--", currentManagerImageScript(spec)]);
|
||||
const liveBeforePayload = captureJsonPayload(liveBefore);
|
||||
@@ -481,7 +451,6 @@ async function providerProfileApply(config: UniDeskConfig, options: ProviderProf
|
||||
target: compactAgentRunLaneStatusTarget(spec),
|
||||
profile: providerProfileSummary(spec, profile, secrets),
|
||||
steps: {
|
||||
activationPreflight,
|
||||
secretSync: secretPayload,
|
||||
gitops: publish.payload,
|
||||
argo: argoPayload,
|
||||
@@ -510,38 +479,6 @@ async function providerProfileApply(config: UniDeskConfig, options: ProviderProf
|
||||
};
|
||||
}
|
||||
|
||||
function providerProfileActivationBlockedResult(
|
||||
spec: AgentRunLaneSpec,
|
||||
profile: string,
|
||||
secrets: readonly AgentRunLaneSecretSpec[],
|
||||
activationPreflight: Record<string, unknown>,
|
||||
stage: "async-submit" | "worker-before-secret-sync",
|
||||
): Record<string, unknown> {
|
||||
return {
|
||||
ok: false,
|
||||
command: "agentrun provider-profile apply",
|
||||
mode: stage === "async-submit" ? "activation-preflight-blocked-before-job" : "activation-preflight-blocked-before-secret-sync",
|
||||
status: "blocked",
|
||||
degradedReason: activationPreflight.reason ?? "existing-runner-provider-secret-activation-risk",
|
||||
phase: "activation-preflight",
|
||||
stage,
|
||||
mutation: false,
|
||||
blockedBeforeMutation: true,
|
||||
jobCreated: stage === "worker-before-secret-sync",
|
||||
jobAlreadyRunning: stage === "worker-before-secret-sync",
|
||||
secretSyncStarted: false,
|
||||
target: { node: spec.nodeId, lane: spec.lane, namespace: spec.runtime.namespace, valuesPrinted: false },
|
||||
profile: providerProfileSummary(spec, profile, secrets),
|
||||
activationPreflight,
|
||||
stagedActivation: providerSecretStagedActivation(spec, [profile]),
|
||||
next: {
|
||||
observeRunners: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane} --dry-run`,
|
||||
retryAfterManagerFence: `bun scripts/cli.ts agentrun provider-profile apply --node ${spec.nodeId} --lane ${spec.lane} --profile ${profile} --confirm`,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function providerProfileApplyProgress(
|
||||
spec: AgentRunLaneSpec,
|
||||
profile: string,
|
||||
@@ -618,17 +555,6 @@ function inspectProviderProfileSources(spec: AgentRunLaneSpec, secrets: readonly
|
||||
});
|
||||
}
|
||||
|
||||
function providerProfileActivationTargets(items: readonly ProviderProfileInspectedSecret[]): ProviderSecretActivationTarget[] {
|
||||
return items.map(({ secret, inspection }) => {
|
||||
if (inspection.sourceValue === null) throw new Error(`provider profile source ${secret.id} is unavailable before activation preflight`);
|
||||
return {
|
||||
id: secret.id,
|
||||
targetRef: secret.targetRef,
|
||||
desiredEncodedFingerprint: providerSecretDesiredEncodedFingerprint(inspection.sourceValue.value),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function providerProfileSourceSummary(items: readonly ProviderProfileInspectedSecret[]): {
|
||||
ready: boolean;
|
||||
count: number;
|
||||
|
||||
@@ -703,34 +703,21 @@ export async function sessionRunBodyFromArgs(sessionId: string, args: string[],
|
||||
|
||||
export function normalizeAipodRenderedQueueTask(task: Record<string, unknown>, args: string[], aipod: string): Record<string, unknown> {
|
||||
if (Object.keys(task).length === 0) return task;
|
||||
const sessionPolicy = readAgentRunClientConfig().client.sessionPolicy;
|
||||
const policyTarget = resolveAgentRunSessionPolicyTarget();
|
||||
const explicitProfile = agentRunOption(args, "profile") ?? agentRunOption(args, "backend-profile");
|
||||
const renderedProfile = stringOrNull(task.backendProfile);
|
||||
const fallbackProfile = sessionPolicy.backendProfile;
|
||||
let backendProfile = explicitProfile ?? renderedProfile ?? fallbackProfile;
|
||||
if (agentRunProviderCredentialRefs(policyTarget.spec, backendProfile).length === 0) {
|
||||
if (explicitProfile !== null) {
|
||||
throw new AgentRunRestError("validation-failed", `${policyTarget.configPath} has no providerCredential Secret binding for explicit --backend-profile ${explicitProfile} on ${policyTarget.spec.nodeId}/${policyTarget.spec.lane}`);
|
||||
const backendProfile = stringOrNull(task.backendProfile);
|
||||
if (backendProfile !== null) {
|
||||
const sessionPolicy = readAgentRunClientConfig().client.sessionPolicy;
|
||||
const policyTarget = resolveAgentRunSessionPolicyTarget();
|
||||
if (agentRunProviderCredentialRefs(policyTarget.spec, backendProfile).length === 0) {
|
||||
throw new AgentRunRestError("validation-failed", `${policyTarget.configPath} has no providerCredential Secret binding for AipodSpec ${aipod} backendProfile=${backendProfile} on ${policyTarget.spec.nodeId}/${policyTarget.spec.lane}`);
|
||||
}
|
||||
backendProfile = fallbackProfile;
|
||||
const basePolicy = record(task.executionPolicy);
|
||||
defaultAgentRunExecutionPolicy(backendProfile, sessionPolicy, policyTarget, {
|
||||
includeToolCredentials: true,
|
||||
basePolicy,
|
||||
});
|
||||
}
|
||||
if (agentRunProviderCredentialRefs(policyTarget.spec, backendProfile).length === 0) {
|
||||
throw new AgentRunRestError("validation-failed", `${policyTarget.configPath} has no providerCredential Secret binding for AipodSpec ${aipod} backendProfile=${backendProfile} on ${policyTarget.spec.nodeId}/${policyTarget.spec.lane}`);
|
||||
}
|
||||
const basePolicy = record(task.executionPolicy);
|
||||
const normalized: Record<string, unknown> = { ...task };
|
||||
const explicitProviderId = agentRunOption(args, "provider-id");
|
||||
normalized.tenantId = stringOrNull(normalized.tenantId) ?? sessionPolicy.tenantId;
|
||||
normalized.projectId = stringOrNull(normalized.projectId) ?? sessionPolicy.projectId;
|
||||
normalized.providerId = explicitProviderId ?? defaultAgentRunProviderId(sessionPolicy, policyTarget);
|
||||
normalized.backendProfile = backendProfile;
|
||||
if (!isRecord(normalized.workspaceRef)) normalized.workspaceRef = cloneJsonRecord(sessionPolicy.workspaceRef);
|
||||
normalized.executionPolicy = defaultAgentRunExecutionPolicy(backendProfile, sessionPolicy, policyTarget, {
|
||||
includeToolCredentials: true,
|
||||
basePolicy: Object.keys(basePolicy).length === 0 ? sessionPolicy.executionPolicy : basePolicy,
|
||||
});
|
||||
return normalized;
|
||||
void args;
|
||||
return { ...task };
|
||||
}
|
||||
|
||||
export function agentRunAipodBindingDisclosure(task: Record<string, unknown>, aipod: string): Record<string, unknown> {
|
||||
|
||||
@@ -39,7 +39,6 @@ import { triggerCurrentYamlLaneConfirmed } from "./cleanup-scripts";
|
||||
import { readAgentRunClientConfig } from "./config";
|
||||
import { displayValue } from "./options";
|
||||
import { pathValue } from "./render";
|
||||
import { providerSecretActivationPreflight, providerSecretDesiredEncodedFingerprint, providerSecretStagedActivation } from "./provider-activation";
|
||||
import { startAsyncAgentRunJob } from "./rest-bridge";
|
||||
import { collectLaneSecretSources, inspectSecretSourceValue, restartYamlLaneScript, secretSyncScript } from "./secrets";
|
||||
import { capture, captureJsonPayload, compactCapture, isGitSha, stringOrNull } from "./utils";
|
||||
@@ -257,66 +256,7 @@ export async function secretSync(config: UniDeskConfig, options: SecretSyncOptio
|
||||
};
|
||||
}
|
||||
const values = inspected.map(({ spec: item, inspection }) => ({ spec: item, value: inspection.sourceValue! }));
|
||||
const providerSecretsByTargetRef = new Map<string, AgentRunLaneSpec["secrets"][number]>();
|
||||
for (const secret of spec.secrets) {
|
||||
if (secret.providerCredentialProfile === null) continue;
|
||||
const targetRef = laneSecretTargetRefKey(secret.targetRef);
|
||||
const existing = providerSecretsByTargetRef.get(targetRef);
|
||||
if (existing !== undefined) {
|
||||
throw new Error(`provider Secret targetRef ${targetRef} is declared by both ${existing.id} and ${secret.id}`);
|
||||
}
|
||||
providerSecretsByTargetRef.set(targetRef, secret);
|
||||
}
|
||||
const providerValues = values.filter(({ spec: item }) => providerSecretsByTargetRef.has(laneSecretTargetRefKey(item.targetRef)));
|
||||
const providerSecrets = providerValues.map(({ spec: item }) => providerSecretsByTargetRef.get(laneSecretTargetRefKey(item.targetRef))!);
|
||||
const providerActivationTargets = providerValues.map(({ spec: item, value }) => {
|
||||
return {
|
||||
id: item.id,
|
||||
targetRef: item.targetRef,
|
||||
desiredEncodedFingerprint: providerSecretDesiredEncodedFingerprint(value.value),
|
||||
};
|
||||
});
|
||||
const activationPreflight = providerValues.length > 0
|
||||
? await providerSecretActivationPreflight(config, spec, providerActivationTargets, { full: options.full || options.raw })
|
||||
: null;
|
||||
const syncValues = values.filter(({ spec: item }) => !providerSecretsByTargetRef.has(laneSecretTargetRefKey(item.targetRef)));
|
||||
if (activationPreflight !== null && activationPreflight.ok !== true) {
|
||||
const profiles = providerSecrets
|
||||
.map((secret) => secret.providerCredentialProfile)
|
||||
.filter((profile): profile is string => profile !== null);
|
||||
return {
|
||||
ok: false,
|
||||
command: "agentrun control-plane secret-sync",
|
||||
mode: options.dryRun || !options.confirm ? "dry-run-blocked-before-mutation" : "confirm-blocked-before-mutation",
|
||||
status: "blocked",
|
||||
mutation: false,
|
||||
blockedBeforeMutation: true,
|
||||
secretSyncStarted: false,
|
||||
configPath,
|
||||
target: { node: spec.nodeId, lane: spec.lane, namespace: spec.runtime.namespace, valuesPrinted: false },
|
||||
degradedReason: activationPreflight.reason ?? "existing-runner-provider-secret-activation-risk",
|
||||
filter: options.secretIds.length === 0 ? null : { secretIds: options.secretIds, selected: sources.map((source) => source.id) },
|
||||
plan: {
|
||||
secretCount: plan.length,
|
||||
items: plan,
|
||||
omittedCount: 0,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
activationPreflight,
|
||||
execution: {
|
||||
providerSecretClassification: "exact-target-ref",
|
||||
providerSecretWriteBlockedCount: providerValues.length,
|
||||
secretSyncStarted: false,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
stagedActivation: providerSecretStagedActivation(spec, profiles),
|
||||
next: {
|
||||
observeRunners: `bun scripts/cli.ts agentrun control-plane cleanup-runners --node ${spec.nodeId} --lane ${spec.lane} --dry-run`,
|
||||
retryAfterManagerFence: `bun scripts/cli.ts agentrun control-plane secret-sync --node ${spec.nodeId} --lane ${spec.lane}${options.secretIds.map((id) => ` --secret-id ${id}`).join("")} --confirm`,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
const syncValues = values;
|
||||
if (options.dryRun || !options.confirm) {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -326,12 +266,9 @@ export async function secretSync(config: UniDeskConfig, options: SecretSyncOptio
|
||||
configPath,
|
||||
target: compactAgentRunLaneStatusTarget(spec),
|
||||
filter: options.secretIds.length === 0 ? null : { secretIds: options.secretIds, selected: sources.map((source) => source.id) },
|
||||
plan: { secretCount: plan.length, items: plan, activationPreflight, valuesPrinted: false },
|
||||
plan: { secretCount: plan.length, items: plan, valuesPrinted: false },
|
||||
execution: {
|
||||
providerSecretClassification: "exact-target-ref",
|
||||
providerSecretWritePolicy: "skip-exact-no-op-until-manager-activation-fence",
|
||||
providerSecretWriteSkippedCount: providerValues.length,
|
||||
writableNonProviderSecretCount: syncValues.length,
|
||||
writableSecretCount: syncValues.length,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
next: {
|
||||
@@ -341,34 +278,6 @@ export async function secretSync(config: UniDeskConfig, options: SecretSyncOptio
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
if (syncValues.length === 0) {
|
||||
return {
|
||||
ok: true,
|
||||
command: "agentrun control-plane secret-sync",
|
||||
mode: "confirmed-provider-secret-no-op-skip",
|
||||
mutation: false,
|
||||
secretSyncStarted: false,
|
||||
providerSecretWriteSkipped: providerValues.length > 0,
|
||||
configPath,
|
||||
target: compactAgentRunLaneStatusTarget(spec),
|
||||
filter: options.secretIds.length === 0 ? null : { secretIds: options.secretIds, selected: sources.map((source) => source.id) },
|
||||
plan: { secretCount: plan.length, items: plan, valuesPrinted: false },
|
||||
activationPreflight,
|
||||
result: {
|
||||
ok: true,
|
||||
status: "skipped-exact-provider-secret-no-op",
|
||||
skippedProviderSecretCount: providerValues.length,
|
||||
providerSecretClassification: "exact-target-ref",
|
||||
reason: "provider-secret-writes-require-agentrun-manager-activation-fence",
|
||||
runtimeSecretValuesDecoded: false,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
next: {
|
||||
status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`,
|
||||
},
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
const result = await capture(config, spec.nodeKubeRoute, ["sh", "--", secretSyncScript(spec, syncValues.map(({ spec: item, value }) => ({ targetRef: item.targetRef, value: value.value })))]);
|
||||
const payload = captureJsonPayload(result);
|
||||
return {
|
||||
@@ -387,7 +296,6 @@ export async function secretSync(config: UniDeskConfig, options: SecretSyncOptio
|
||||
writableNonProviderSecretCount: syncValues.length,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
activationPreflight,
|
||||
result: payload,
|
||||
capture: compactCapture(result, { full: result.exitCode !== 0, stdoutTailChars: 3000, stderrTailChars: 3000 }),
|
||||
next: {
|
||||
@@ -397,10 +305,6 @@ export async function secretSync(config: UniDeskConfig, options: SecretSyncOptio
|
||||
};
|
||||
}
|
||||
|
||||
function laneSecretTargetRefKey(targetRef: { namespace: string; name: string; key: string }): string {
|
||||
return `${targetRef.namespace}/${targetRef.name}/${targetRef.key}`;
|
||||
}
|
||||
|
||||
export async function exposeAgentRun(_config: UniDeskConfig, options: ConfirmOptions): Promise<Record<string, unknown>> {
|
||||
const clientConfig = readAgentRunClientConfig();
|
||||
const exposure = clientConfig.publicExposure;
|
||||
|
||||
Reference in New Issue
Block a user