fix: 修复 runner Codex shell 工具环境
This commit is contained in:
+12
-3
@@ -248,7 +248,7 @@ async function sessionTurn(args: ParsedArgs, positionalSessionId: string | null)
|
|||||||
copyOptionalFlag(args, runnerBody, "attempt-id", "attemptId");
|
copyOptionalFlag(args, runnerBody, "attempt-id", "attemptId");
|
||||||
copyOptionalFlag(args, runnerBody, "runner-id", "runnerId");
|
copyOptionalFlag(args, runnerBody, "runner-id", "runnerId");
|
||||||
copyOptionalFlag(args, runnerBody, "source-commit", "sourceCommit");
|
copyOptionalFlag(args, runnerBody, "source-commit", "sourceCommit");
|
||||||
copyOptionalFlag(args, runnerBody, "runner-manager-url", "managerUrl");
|
copyRunnerManagerUrlFlag(args, runnerBody);
|
||||||
copyOptionalFlag(args, runnerBody, "service-account-name", "serviceAccountName");
|
copyOptionalFlag(args, runnerBody, "service-account-name", "serviceAccountName");
|
||||||
const runnerIdempotencyKey = optionalFlag(args, "runner-idempotency-key");
|
const runnerIdempotencyKey = optionalFlag(args, "runner-idempotency-key");
|
||||||
if (runnerIdempotencyKey) runnerBody.idempotencyKey = runnerIdempotencyKey;
|
if (runnerIdempotencyKey) runnerBody.idempotencyKey = runnerIdempotencyKey;
|
||||||
@@ -314,7 +314,7 @@ async function dispatchQueueTask(args: ParsedArgs, taskId: string): Promise<Json
|
|||||||
copy("attempt-id", "attemptId");
|
copy("attempt-id", "attemptId");
|
||||||
copy("runner-id", "runnerId");
|
copy("runner-id", "runnerId");
|
||||||
copy("source-commit", "sourceCommit");
|
copy("source-commit", "sourceCommit");
|
||||||
copy("runner-manager-url", "managerUrl");
|
copyRunnerManagerUrlFlag(args, body);
|
||||||
copy("service-account-name", "serviceAccountName");
|
copy("service-account-name", "serviceAccountName");
|
||||||
return client(args).post(`/api/v1/queue/tasks/${encodeURIComponent(taskId)}/dispatch`, body);
|
return client(args).post(`/api/v1/queue/tasks/${encodeURIComponent(taskId)}/dispatch`, body);
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ async function renderRunnerJob(args: ParsedArgs): Promise<JsonRecord> {
|
|||||||
if (attemptId) body.attemptId = attemptId;
|
if (attemptId) body.attemptId = attemptId;
|
||||||
if (runnerId) body.runnerId = runnerId;
|
if (runnerId) body.runnerId = runnerId;
|
||||||
if (sourceCommit) body.sourceCommit = sourceCommit;
|
if (sourceCommit) body.sourceCommit = sourceCommit;
|
||||||
if (runnerManagerUrl) body.managerUrl = runnerManagerUrl;
|
if (runnerManagerUrl) body.managerUrl = resolveRunnerManagerUrlFlag(args, runnerManagerUrl);
|
||||||
if (idempotencyKey) body.idempotencyKey = idempotencyKey;
|
if (idempotencyKey) body.idempotencyKey = idempotencyKey;
|
||||||
return await client(args).post(`/api/v1/runs/${encodeURIComponent(runId)}/runner-jobs`, body) as JsonRecord;
|
return await client(args).post(`/api/v1/runs/${encodeURIComponent(runId)}/runner-jobs`, body) as JsonRecord;
|
||||||
}
|
}
|
||||||
@@ -754,6 +754,15 @@ function copyOptionalFlag(args: ParsedArgs, target: JsonRecord, flagName: string
|
|||||||
if (value) target[key] = value;
|
if (value) target[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyRunnerManagerUrlFlag(args: ParsedArgs, target: JsonRecord): void {
|
||||||
|
const value = optionalFlag(args, "runner-manager-url");
|
||||||
|
if (value) target.managerUrl = resolveRunnerManagerUrlFlag(args, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveRunnerManagerUrlFlag(args: ParsedArgs, value: string): string {
|
||||||
|
return value === "auto" ? managerUrl(args) : value;
|
||||||
|
}
|
||||||
|
|
||||||
function readerQuery(args: ParsedArgs): string {
|
function readerQuery(args: ParsedArgs): string {
|
||||||
const readerId = optionalFlag(args, "reader-id");
|
const readerId = optionalFlag(args, "reader-id");
|
||||||
return readerId ? `?readerId=${encodeURIComponent(readerId)}` : "";
|
return readerId ? `?readerId=${encodeURIComponent(readerId)}` : "";
|
||||||
|
|||||||
@@ -46,12 +46,15 @@ export function createBackendSession(run: RunRecord, options: BackendAdapterOpti
|
|||||||
|
|
||||||
export function backendTurnOptions(run: RunRecord, command: CommandRecord, options: BackendAdapterOptions = {}): CodexStdioTurnOptions {
|
export function backendTurnOptions(run: RunRecord, command: CommandRecord, options: BackendAdapterOptions = {}): CodexStdioTurnOptions {
|
||||||
const prompt = typeof command.payload.prompt === "string" ? command.payload.prompt : JSON.stringify(command.payload);
|
const prompt = typeof command.payload.prompt === "string" ? command.payload.prompt : JSON.stringify(command.payload);
|
||||||
|
const sandboxOverride = codexShellSandboxOverride(options.env ?? process.env);
|
||||||
const turnOptions: CodexStdioTurnOptions = {
|
const turnOptions: CodexStdioTurnOptions = {
|
||||||
backendProfile: run.backendProfile,
|
backendProfile: run.backendProfile,
|
||||||
prompt,
|
prompt,
|
||||||
cwd: options.workspacePath ?? (typeof run.workspaceRef.path === "string" ? run.workspaceRef.path : process.cwd()),
|
cwd: options.workspacePath ?? (typeof run.workspaceRef.path === "string" ? run.workspaceRef.path : process.cwd()),
|
||||||
approvalPolicy: run.executionPolicy.approval,
|
approvalPolicy: run.executionPolicy.approval,
|
||||||
sandbox: run.executionPolicy.sandbox,
|
sandbox: sandboxOverride ?? run.executionPolicy.sandbox,
|
||||||
|
requestedSandbox: run.executionPolicy.sandbox,
|
||||||
|
sandboxOverrideSource: sandboxOverride ? "AGENTRUN_CODEX_SHELL_SANDBOX" : null,
|
||||||
timeoutMs: run.executionPolicy.timeoutMs,
|
timeoutMs: run.executionPolicy.timeoutMs,
|
||||||
};
|
};
|
||||||
if (typeof command.payload.model === "string") turnOptions.model = command.payload.model;
|
if (typeof command.payload.model === "string") turnOptions.model = command.payload.model;
|
||||||
@@ -67,3 +70,8 @@ export function backendTurnOptions(run: RunRecord, command: CommandRecord, optio
|
|||||||
if (options.onActiveTurn) turnOptions.onActiveTurn = options.onActiveTurn;
|
if (options.onActiveTurn) turnOptions.onActiveTurn = options.onActiveTurn;
|
||||||
return turnOptions;
|
return turnOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function codexShellSandboxOverride(env: NodeJS.ProcessEnv): string | null {
|
||||||
|
const value = env.AGENTRUN_CODEX_SHELL_SANDBOX?.trim();
|
||||||
|
return value && value.length > 0 ? value : null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export interface CodexStdioTurnOptions {
|
|||||||
threadId?: string;
|
threadId?: string;
|
||||||
approvalPolicy: string;
|
approvalPolicy: string;
|
||||||
sandbox: string;
|
sandbox: string;
|
||||||
|
requestedSandbox?: string;
|
||||||
|
sandboxOverrideSource?: string | null;
|
||||||
timeoutMs: number;
|
timeoutMs: number;
|
||||||
command?: string;
|
command?: string;
|
||||||
args?: string[];
|
args?: string[];
|
||||||
@@ -1065,6 +1067,12 @@ function backendMetadata(options: CodexStdioTurnOptions): JsonRecord {
|
|||||||
backendKind: spec?.backendKind ?? "codex-app-server-stdio",
|
backendKind: spec?.backendKind ?? "codex-app-server-stdio",
|
||||||
protocol: spec?.protocol ?? codexProtocol,
|
protocol: spec?.protocol ?? codexProtocol,
|
||||||
transport: spec?.transport ?? "stdio",
|
transport: spec?.transport ?? "stdio",
|
||||||
|
sandbox: {
|
||||||
|
requested: options.requestedSandbox ?? options.sandbox,
|
||||||
|
effective: options.sandbox,
|
||||||
|
overrideSource: options.sandboxOverrideSource ?? null,
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { backendProfileSpec } from "../common/backend-profiles.js";
|
|||||||
|
|
||||||
const defaultBootRepoUrl = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git";
|
const defaultBootRepoUrl = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git";
|
||||||
const defaultResourceBinPath = "/usr/local/bin";
|
const defaultResourceBinPath = "/usr/local/bin";
|
||||||
|
const defaultCodexShellSandbox = "danger-full-access";
|
||||||
|
|
||||||
export interface RunnerJobRenderOptions {
|
export interface RunnerJobRenderOptions {
|
||||||
run: RunRecord;
|
run: RunRecord;
|
||||||
@@ -177,6 +178,7 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
|
|||||||
{ name: "AGENTRUN_RUNNER_ID", value: context.runnerId },
|
{ name: "AGENTRUN_RUNNER_ID", value: context.runnerId },
|
||||||
{ name: "AGENTRUN_BACKEND_PROFILE", value: options.run.backendProfile },
|
{ name: "AGENTRUN_BACKEND_PROFILE", value: options.run.backendProfile },
|
||||||
{ name: "AGENTRUN_EXECUTION_POLICY_JSON", value: JSON.stringify(options.run.executionPolicy) },
|
{ name: "AGENTRUN_EXECUTION_POLICY_JSON", value: JSON.stringify(options.run.executionPolicy) },
|
||||||
|
{ name: "AGENTRUN_CODEX_SHELL_SANDBOX", value: codexShellSandbox(options.run.executionPolicy) },
|
||||||
{ name: "AGENTRUN_SESSION_REF_JSON", value: JSON.stringify(options.run.sessionRef ?? null) },
|
{ name: "AGENTRUN_SESSION_REF_JSON", value: JSON.stringify(options.run.sessionRef ?? null) },
|
||||||
{ name: "AGENTRUN_RESOURCE_BUNDLE_JSON", value: JSON.stringify(options.run.resourceBundleRef ?? null) },
|
{ name: "AGENTRUN_RESOURCE_BUNDLE_JSON", value: JSON.stringify(options.run.resourceBundleRef ?? null) },
|
||||||
{ name: "AGENTRUN_WORKSPACE_ROOT", value: "/home/agentrun/workspaces" },
|
{ name: "AGENTRUN_WORKSPACE_ROOT", value: "/home/agentrun/workspaces" },
|
||||||
@@ -205,6 +207,11 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function codexShellSandbox(policy: ExecutionPolicy): string {
|
||||||
|
if (policy.sandbox === "workspace-write") return defaultCodexShellSandbox;
|
||||||
|
return policy.sandbox;
|
||||||
|
}
|
||||||
|
|
||||||
function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[] {
|
function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[] {
|
||||||
return items.map((item) => ({
|
return items.map((item) => ({
|
||||||
name: item.envName,
|
name: item.envName,
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export async function materializeResourceBundle(resourceBundleRef: ResourceBundl
|
|||||||
};
|
};
|
||||||
const defaultCheckout = await checkoutFor(defaultSource);
|
const defaultCheckout = await checkoutFor(defaultSource);
|
||||||
const materializedBundles = await materializeGitBundles(workspacePath, resourceBundleRef, defaultSource, defaultCheckout, checkoutFor);
|
const materializedBundles = await materializeGitBundles(workspacePath, resourceBundleRef, defaultSource, defaultCheckout, checkoutFor);
|
||||||
const tools = await prepareGitBundleTools(workspacePath);
|
const tools = await prepareGitBundleTools(workspacePath, env);
|
||||||
const skills = await discoverGitBundleSkills(workspacePath);
|
const skills = await discoverGitBundleSkills(workspacePath);
|
||||||
const prompts = await materializePromptRefs(defaultCheckout.checkoutPath, resourceBundleRef.promptRefs ?? []);
|
const prompts = await materializePromptRefs(defaultCheckout.checkoutPath, resourceBundleRef.promptRefs ?? []);
|
||||||
const initialPrompt = assembleInitialPrompt(prompts.items, skills.items);
|
const initialPrompt = assembleInitialPrompt(prompts.items, skills.items);
|
||||||
@@ -193,28 +193,50 @@ function optionalNonEmpty(value: unknown): string | undefined {
|
|||||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function prepareGitBundleTools(workspacePath: string): Promise<{ binPath?: string; event: JsonRecord }> {
|
async function prepareGitBundleTools(workspacePath: string, env: NodeJS.ProcessEnv): Promise<{ binPath?: string; event: JsonRecord }> {
|
||||||
const binPath = path.join(workspacePath, "tools");
|
const sourceBinPath = path.join(workspacePath, "tools");
|
||||||
|
const installedBinPath = optionalNonEmpty(env.AGENTRUN_RESOURCE_BIN_PATH);
|
||||||
|
const runtimeBinPath = installedBinPath ?? sourceBinPath;
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = await readdir(binPath, { withFileTypes: true });
|
entries = await readdir(sourceBinPath, { withFileTypes: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && typeof error === "object" && "code" in error && (error as { code?: unknown }).code === "ENOENT") return { event: { count: 0, names: [], binPath: null, valuesPrinted: false } };
|
if (error && typeof error === "object" && "code" in error && (error as { code?: unknown }).code === "ENOENT") return { event: { count: 0, names: [], binPath: null, sourceBinPath: null, installedBinPath: null, installed: false, valuesPrinted: false } };
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
const names: string[] = [];
|
const names: string[] = [];
|
||||||
const items: JsonRecord[] = [];
|
const items: JsonRecord[] = [];
|
||||||
|
if (installedBinPath) await mkdir(installedBinPath, { recursive: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.isFile()) continue;
|
if (!entry.isFile()) continue;
|
||||||
const filePath = path.join(binPath, entry.name);
|
const filePath = path.join(sourceBinPath, entry.name);
|
||||||
const text = await readFile(filePath, "utf8");
|
const text = await readFile(filePath, "utf8");
|
||||||
const firstLine = text.split(/\r?\n/u, 1)[0] ?? "";
|
const firstLine = text.split(/\r?\n/u, 1)[0] ?? "";
|
||||||
if (!firstLine.startsWith("#!")) continue;
|
if (!firstLine.startsWith("#!")) continue;
|
||||||
await chmod(filePath, 0o755);
|
await chmod(filePath, 0o755);
|
||||||
|
if (installedBinPath) {
|
||||||
|
const targetPath = path.join(installedBinPath, entry.name);
|
||||||
|
if (targetPath !== filePath) {
|
||||||
|
await cp(filePath, targetPath, { force: true, dereference: false });
|
||||||
|
await chmod(targetPath, 0o755);
|
||||||
|
}
|
||||||
|
}
|
||||||
names.push(entry.name);
|
names.push(entry.name);
|
||||||
items.push({ name: entry.name, sha256: sha256Text(text), bytes: Buffer.byteLength(text, "utf8"), shebang: firstLine.slice(0, 80), valuesPrinted: false });
|
items.push({ name: entry.name, sha256: sha256Text(text), bytes: Buffer.byteLength(text, "utf8"), shebang: firstLine.slice(0, 80), valuesPrinted: false });
|
||||||
}
|
}
|
||||||
return { binPath, event: { count: names.length, names, items, binPath: pathSummary(binPath), valuesPrinted: false } };
|
return {
|
||||||
|
...(names.length > 0 ? { binPath: runtimeBinPath } : {}),
|
||||||
|
event: {
|
||||||
|
count: names.length,
|
||||||
|
names,
|
||||||
|
items,
|
||||||
|
binPath: names.length > 0 ? pathSummary(runtimeBinPath) : null,
|
||||||
|
sourceBinPath: pathSummary(sourceBinPath),
|
||||||
|
installedBinPath: installedBinPath ? pathSummary(installedBinPath) : null,
|
||||||
|
installed: Boolean(installedBinPath && names.length > 0),
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function materializePromptRefs(checkoutPath: string, refs: NonNullable<ResourceBundleRef["promptRefs"]>): Promise<{ items: MaterializedPromptRef[]; event: JsonRecord }> {
|
async function materializePromptRefs(checkoutPath: string, refs: NonNullable<ResourceBundleRef["promptRefs"]>): Promise<{ items: MaterializedPromptRef[]; event: JsonRecord }> {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const selfTest: SelfTestCase = async (context) => {
|
|||||||
assertRunnerJobUsesWritableCodexHome(rendered.manifest as JsonRecord, context.codexHome, "codex-0", "/var/run/agentrun/secrets/codex-0");
|
assertRunnerJobUsesWritableCodexHome(rendered.manifest as JsonRecord, context.codexHome, "codex-0", "/var/run/agentrun/secrets/codex-0");
|
||||||
assertRunnerJobUsesToolCredential(rendered, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
|
assertRunnerJobUsesToolCredential(rendered, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
|
||||||
assertRunnerJobUsesToolCredential(rendered, "UNIDESK_SSH_CLIENT_TOKEN", "agentrun-v01-tool-unidesk-ssh", "UNIDESK_SSH_CLIENT_TOKEN");
|
assertRunnerJobUsesToolCredential(rendered, "UNIDESK_SSH_CLIENT_TOKEN", "agentrun-v01-tool-unidesk-ssh", "UNIDESK_SSH_CLIENT_TOKEN");
|
||||||
|
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "AGENTRUN_CODEX_SHELL_SANDBOX"), "danger-full-access");
|
||||||
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_API_KEY"), "REDACTED");
|
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_API_KEY"), "REDACTED");
|
||||||
assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_API_KEY"]);
|
assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_API_KEY"]);
|
||||||
assertNoSecretLeak(rendered);
|
assertNoSecretLeak(rendered);
|
||||||
@@ -227,7 +228,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
|||||||
assert.equal(envMap.get("AGENTRUN_SESSION_PVC_NAMESPACE"), "agentrun-v01");
|
assert.equal(envMap.get("AGENTRUN_SESSION_PVC_NAMESPACE"), "agentrun-v01");
|
||||||
assert.equal(envMap.get("AGENTRUN_SESSION_PVC_MOUNT_PATH"), "/home/agentrun/.codex-codex/sessions");
|
assert.equal(envMap.get("AGENTRUN_SESSION_PVC_MOUNT_PATH"), "/home/agentrun/.codex-codex/sessions");
|
||||||
assert.equal(envMap.get("AGENTRUN_CODEX_ROLLOUT_SUBDIR"), "sessions");
|
assert.equal(envMap.get("AGENTRUN_CODEX_ROLLOUT_SUBDIR"), "sessions");
|
||||||
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-minimax-m3-profile-dry-run", "runner-k8s-job-dsflash-go-profile-dry-run", "runner-k8s-job-dsflash-go-legacy-secretref-normalized", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-tool-credential-env", "runner-job-unidesk-ssh-tool-credential-env", "runner-job-unidesk-ssh-transient-env-denied", "runner-k8s-job-session-pvc-volume-and-env"] };
|
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-codex-shell-sandbox-env", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-minimax-m3-profile-dry-run", "runner-k8s-job-dsflash-go-profile-dry-run", "runner-k8s-job-dsflash-go-legacy-secretref-normalized", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-tool-credential-env", "runner-job-unidesk-ssh-tool-credential-env", "runner-job-unidesk-ssh-transient-env-denied", "runner-k8s-job-session-pvc-volume-and-env"] };
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ const selfTest: SelfTestCase = async (context) => {
|
|||||||
const finalCommand = await client.get(`/api/v1/runs/${happy.runId}/commands/${happy.commandId}`) as { state?: string };
|
const finalCommand = await client.get(`/api/v1/runs/${happy.runId}/commands/${happy.commandId}`) as { state?: string };
|
||||||
assert.equal(finalCommand.state, "completed");
|
assert.equal(finalCommand.state, "completed");
|
||||||
|
|
||||||
|
const sandboxOverride = await createRunWithCommand(client, context, "hello sandbox override", "selftest-sandbox-override", 15_000);
|
||||||
|
const sandboxOverrideResult = await runOnce({ managerUrl: server.baseUrl, runId: sandboxOverride.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_CODEX_SHELL_SANDBOX: "danger-full-access", AGENTRUN_FAKE_CODEX_MODE: "require-danger-sandbox" }, oneShot: true });
|
||||||
|
assert.equal(sandboxOverrideResult.terminalStatus, "completed");
|
||||||
|
const sandboxEvents = await client.get(`/api/v1/runs/${sandboxOverride.runId}/events?afterSeq=0&limit=100`) as { items?: Array<{ type: string; payload: JsonRecord }> };
|
||||||
|
const sandboxStarting = sandboxEvents.items?.find((event) => event.type === "backend_status" && event.payload.phase === "codex-app-server-starting");
|
||||||
|
assert.equal(((sandboxStarting?.payload.sandbox as JsonRecord | undefined)?.requested), "workspace-write");
|
||||||
|
assert.equal(((sandboxStarting?.payload.sandbox as JsonRecord | undefined)?.effective), "danger-full-access");
|
||||||
|
assert.equal(((sandboxStarting?.payload.sandbox as JsonRecord | undefined)?.overrideSource), "AGENTRUN_CODEX_SHELL_SANDBOX");
|
||||||
|
|
||||||
await runLeaseConflictRecoveryCase({ client, managerUrl: server.baseUrl, context });
|
await runLeaseConflictRecoveryCase({ client, managerUrl: server.baseUrl, context });
|
||||||
|
|
||||||
const projectedHome = path.join(context.tmp, "runtime-codex-home");
|
const projectedHome = path.join(context.tmp, "runtime-codex-home");
|
||||||
@@ -230,7 +239,7 @@ const selfTest: SelfTestCase = async (context) => {
|
|||||||
await runSessionStorageSubdirCase({ client, managerUrl: server.baseUrl, context });
|
await runSessionStorageSubdirCase({ client, managerUrl: server.baseUrl, context });
|
||||||
await runSessionStorageNoSecretLeakCase({ client, managerUrl: server.baseUrl, context });
|
await runSessionStorageNoSecretLeakCase({ client, managerUrl: server.baseUrl, context });
|
||||||
|
|
||||||
return { name: "codex-stdio", tests: ["runner-lease-heartbeat", "runner-lease-conflict-recovery", "codex-stdio-fake-turn", "codex-stdio-projected-writable-home", "codex-stdio-deepseek-profile-fake-turn", "codex-stdio-dsflash-go-profile-fake-turn", "codex-stdio-dsflash-go-config-metadata", "codex-stdio-minimax-m3-profile-fake-turn", "codex-stdio-deepseek-missing-secret-no-fallback", "codex-stdio-minimax-m3-missing-secret-no-fallback", "codex-stdio-config-model-authoritative", "codex-stdio-explicit-model-forwarded", "codex-stdio-final-agent-message-only", "codex-stdio-web-search-progress", "codex-stdio-stale-thread-resume-failed", "codex-stdio-live-tool-events", "codex-stdio-noisy-reasoning-suppression", "codex-stdio-missing-turn-result", "codex-stdio-provider-auth-failed", "codex-stdio-provider-rate-limited", "codex-stdio-provider-invalid-tool-call", "codex-stdio-provider-compact-unsupported", "codex-stdio-provider-503-rpc-error", "codex-stdio-provider-503-terminal", "codex-stdio-provider-503-retry-event", "codex-stdio-invalid-json", "codex-stdio-timeout", "codex-stdio-idle-timeout-progress-refresh", "codex-stdio-command-failure-keeps-run-open", "codex-stdio-secret-unavailable", "codex-stdio-spawn-failure"] };
|
return { name: "codex-stdio", tests: ["runner-lease-heartbeat", "runner-lease-conflict-recovery", "codex-stdio-fake-turn", "codex-stdio-k8s-sandbox-override", "codex-stdio-projected-writable-home", "codex-stdio-deepseek-profile-fake-turn", "codex-stdio-dsflash-go-profile-fake-turn", "codex-stdio-dsflash-go-config-metadata", "codex-stdio-minimax-m3-profile-fake-turn", "codex-stdio-deepseek-missing-secret-no-fallback", "codex-stdio-minimax-m3-missing-secret-no-fallback", "codex-stdio-config-model-authoritative", "codex-stdio-explicit-model-forwarded", "codex-stdio-final-agent-message-only", "codex-stdio-web-search-progress", "codex-stdio-stale-thread-resume-failed", "codex-stdio-live-tool-events", "codex-stdio-noisy-reasoning-suppression", "codex-stdio-missing-turn-result", "codex-stdio-provider-auth-failed", "codex-stdio-provider-rate-limited", "codex-stdio-provider-invalid-tool-call", "codex-stdio-provider-compact-unsupported", "codex-stdio-provider-503-rpc-error", "codex-stdio-provider-503-terminal", "codex-stdio-provider-503-retry-event", "codex-stdio-invalid-json", "codex-stdio-timeout", "codex-stdio-idle-timeout-progress-refresh", "codex-stdio-command-failure-keeps-run-open", "codex-stdio-secret-unavailable", "codex-stdio-spawn-failure"] };
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
await new Promise<void>((resolve) => server.server.close(() => resolve()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { execFile as execFileCallback } from "node:child_process";
|
import { execFile as execFileCallback } from "node:child_process";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
import { access, chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { startManagerServer } from "../../mgr/server.js";
|
import { startManagerServer } from "../../mgr/server.js";
|
||||||
import { ManagerClient } from "../../mgr/client.js";
|
import { ManagerClient } from "../../mgr/client.js";
|
||||||
@@ -79,8 +79,12 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
|||||||
);
|
);
|
||||||
|
|
||||||
const sessionRun = await createHwlabRun(client, context, bundle, "hwlab-session-resume", "hello session", "hwlab-command-session");
|
const sessionRun = await createHwlabRun(client, context, bundle, "hwlab-session-resume", "hello session", "hwlab-command-session");
|
||||||
const runResult = await runOnce({ managerUrl: server.baseUrl, runId: sessionRun.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "workspaces") }, oneShot: true });
|
const resourceBinPath = path.join(context.tmp, "resource-bin");
|
||||||
|
const runResult = await runOnce({ managerUrl: server.baseUrl, runId: sessionRun.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "workspaces"), AGENTRUN_RESOURCE_BIN_PATH: resourceBinPath }, oneShot: true });
|
||||||
assert.equal(runResult.terminalStatus, "completed");
|
assert.equal(runResult.terminalStatus, "completed");
|
||||||
|
await access(path.join(resourceBinPath, "hwpod"));
|
||||||
|
const resourceBinExec = await execFile(path.join(resourceBinPath, "hwpod"), ["--selftest"]);
|
||||||
|
assert.match(resourceBinExec.stdout, /hwpod-selftest/u);
|
||||||
const session = await store.getSession("hwlab-session-resume");
|
const session = await store.getSession("hwlab-session-resume");
|
||||||
assert.equal(session?.threadId, "thread_selftest_1");
|
assert.equal(session?.threadId, "thread_selftest_1");
|
||||||
const resultEnvelope = await client.get(`/api/v1/runs/${sessionRun.runId}/commands/${sessionRun.commandId}/result`) as JsonRecord;
|
const resultEnvelope = await client.get(`/api/v1/runs/${sessionRun.runId}/commands/${sessionRun.commandId}/result`) as JsonRecord;
|
||||||
@@ -93,6 +97,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
|||||||
assert.deepEqual(resultBundleTargets, ["tools", ".agents/skills"]);
|
assert.deepEqual(resultBundleTargets, ["tools", ".agents/skills"]);
|
||||||
const materialized = ((resultEnvelope.resourceBundleRef as JsonRecord).materialized as JsonRecord);
|
const materialized = ((resultEnvelope.resourceBundleRef as JsonRecord).materialized as JsonRecord);
|
||||||
assert.deepEqual(((materialized.tools as JsonRecord).names), ["hwpod"]);
|
assert.deepEqual(((materialized.tools as JsonRecord).names), ["hwpod"]);
|
||||||
|
assert.equal(((materialized.tools as JsonRecord).installed), true);
|
||||||
assert.deepEqual(((materialized.skillDirs as JsonRecord).names), ["hwpod-cli", "hwpod-ctl"]);
|
assert.deepEqual(((materialized.skillDirs as JsonRecord).names), ["hwpod-cli", "hwpod-ctl"]);
|
||||||
assertNoSecretLeak(resultEnvelope);
|
assertNoSecretLeak(resultEnvelope);
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ for await (const line of rl) {
|
|||||||
}
|
}
|
||||||
if (message.method === "thread/start") {
|
if (message.method === "thread/start") {
|
||||||
observedThreadModel = Object.hasOwn(message.params ?? {}, "model");
|
observedThreadModel = Object.hasOwn(message.params ?? {}, "model");
|
||||||
|
if (mode === "require-danger-sandbox" && message.params?.sandbox !== "danger-full-access") {
|
||||||
|
respond(message.id, null, { code: -32000, message: `thread/start expected danger-full-access sandbox, got ${String(message.params?.sandbox ?? "missing")}` });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (mode === "reject-unexpected-model" && observedThreadModel) {
|
if (mode === "reject-unexpected-model" && observedThreadModel) {
|
||||||
respond(message.id, null, { code: -32000, message: "thread/start unexpectedly included model" });
|
respond(message.id, null, { code: -32000, message: "thread/start unexpectedly included model" });
|
||||||
continue;
|
continue;
|
||||||
@@ -39,6 +43,10 @@ for await (const line of rl) {
|
|||||||
}
|
}
|
||||||
if (message.method === "thread/resume") {
|
if (message.method === "thread/resume") {
|
||||||
observedThreadModel = Object.hasOwn(message.params ?? {}, "model");
|
observedThreadModel = Object.hasOwn(message.params ?? {}, "model");
|
||||||
|
if (mode === "require-danger-sandbox" && message.params?.sandbox !== "danger-full-access") {
|
||||||
|
respond(message.id, null, { code: -32000, message: `thread/resume expected danger-full-access sandbox, got ${String(message.params?.sandbox ?? "missing")}` });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (mode === "resume-no-rollout") {
|
if (mode === "resume-no-rollout") {
|
||||||
respond(message.id, null, { code: -32000, message: `no rollout found for thread id ${String(message.params?.threadId ?? "unknown")}` });
|
respond(message.id, null, { code: -32000, message: `no rollout found for thread id ${String(message.params?.threadId ?? "unknown")}` });
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user