mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(onboarding): harden live inference handoff (#107041)
* fix(onboarding): harden live inference handoff * fix(crestodian): preserve auto runtime probes * fix(ci): restore current main lint gates
This commit is contained in:
committed by
GitHub
parent
f72e936f26
commit
27f2a45c54
@@ -131,12 +131,15 @@ function createStartOptions(
|
||||
return {
|
||||
transport: "stdio",
|
||||
command: "codex",
|
||||
commandSource: "resolved-managed",
|
||||
args: ["app-server"],
|
||||
headers: { authorization: "Bearer dev-token" },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
const EPHEMERAL_AUTH_ARGS = ["app-server", "-c", 'cli_auth_credentials_store="ephemeral"'];
|
||||
|
||||
async function expectPathMissing(filePath: string): Promise<void> {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
@@ -226,6 +229,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: {
|
||||
CODEX_HOME: codexHome,
|
||||
},
|
||||
@@ -254,6 +258,56 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("places the ephemeral auth-store override after configured root overrides", async () => {
|
||||
await withTempDir("openclaw-codex-auth-store-", async (agentDir) => {
|
||||
const startOptions = createStartOptions({
|
||||
args: ["-c", 'cli_auth_credentials_store="keyring"', "app-server"],
|
||||
});
|
||||
|
||||
const bridged = await bridgeCodexAppServerStartOptions({ startOptions, agentDir });
|
||||
|
||||
expect(bridged.args).toEqual([
|
||||
"-c",
|
||||
'cli_auth_credentials_store="keyring"',
|
||||
"app-server",
|
||||
"-c",
|
||||
'cli_auth_credentials_store="ephemeral"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not mistake an option value for the app-server subcommand", async () => {
|
||||
await withTempDir("openclaw-codex-profile-name-", async (agentDir) => {
|
||||
const startOptions = createStartOptions({
|
||||
args: ["--profile", "app-server", "app-server"],
|
||||
});
|
||||
|
||||
const bridged = await bridgeCodexAppServerStartOptions({ startOptions, agentDir });
|
||||
|
||||
expect(bridged.args).toEqual([
|
||||
"--profile",
|
||||
"app-server",
|
||||
"app-server",
|
||||
"-c",
|
||||
'cli_auth_credentials_store="ephemeral"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves custom stdio backend arguments", async () => {
|
||||
await withTempDir("openclaw-codex-custom-backend-", async (agentDir) => {
|
||||
const startOptions = createStartOptions({
|
||||
command: "custom-codex-compatible-server",
|
||||
commandSource: "config",
|
||||
args: [],
|
||||
});
|
||||
|
||||
const bridged = await bridgeCodexAppServerStartOptions({ startOptions, agentDir });
|
||||
|
||||
expect(bridged.args).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves inherited HOME when clearEnv asks to clear app-server isolation vars", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
|
||||
const startOptions = createStartOptions({
|
||||
@@ -267,6 +321,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: {
|
||||
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
|
||||
},
|
||||
@@ -294,6 +349,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: {
|
||||
CODEX_HOME: codexHome,
|
||||
HOME: nativeHome,
|
||||
@@ -336,6 +392,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: {
|
||||
EXISTING: "1",
|
||||
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
|
||||
@@ -374,6 +431,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: {
|
||||
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
|
||||
},
|
||||
@@ -406,6 +464,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: {
|
||||
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
|
||||
},
|
||||
@@ -438,6 +497,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
});
|
||||
expect(bridged).toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: { CODEX_HOME: resolveCodexAppServerHomeDir(agentDir) },
|
||||
clearEnv: ["FOO", "OPENAI_API_KEY", "CODEX_API_KEY", "CODEX_ACCESS_TOKEN"],
|
||||
});
|
||||
@@ -608,6 +668,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions,
|
||||
args: EPHEMERAL_AUTH_ARGS,
|
||||
env: {
|
||||
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type OAuthCredential,
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { resolveCodexAppServerHomeDir, withEphemeralCodexAuthStore } from "./auth-start-options.js";
|
||||
import type { CodexAppServerClient } from "./client.js";
|
||||
import { ensureCodexComputerUseSharedPluginCache } from "./computer-use-cache.js";
|
||||
import {
|
||||
@@ -46,7 +47,6 @@ const OPENAI_PROVIDER = "openai";
|
||||
const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai:default";
|
||||
const CODEX_HOME_ENV_VAR = "CODEX_HOME";
|
||||
const HOME_ENV_VAR = "HOME";
|
||||
const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
|
||||
const CODEX_API_KEY_ENV_VAR = "CODEX_API_KEY";
|
||||
const OPENAI_API_KEY_ENV_VAR = "OPENAI_API_KEY";
|
||||
const CODEX_ACCESS_TOKEN_ENV_VAR = "CODEX_ACCESS_TOKEN";
|
||||
@@ -59,7 +59,6 @@ const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
|
||||
const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
|
||||
const CODEX_AUTH_JSON_FILENAME = "auth.json";
|
||||
const CODEX_HOME_DIRNAME = ".codex";
|
||||
|
||||
type AuthProfileOrderConfig = Parameters<typeof resolveAuthProfileOrder>[0]["cfg"];
|
||||
const scopedOAuthRefreshQueues = new WeakMap<
|
||||
AuthProfileStore,
|
||||
@@ -79,7 +78,7 @@ export async function bridgeCodexAppServerStartOptions(params: {
|
||||
return params.startOptions;
|
||||
}
|
||||
const scopedStartOptions = await withCodexHomeEnvironment(
|
||||
params.startOptions,
|
||||
withEphemeralCodexAuthStore(params),
|
||||
params.agentDir,
|
||||
params.pluginConfig,
|
||||
);
|
||||
@@ -419,9 +418,7 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey: string): string {
|
||||
return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
|
||||
}
|
||||
|
||||
export function resolveCodexAppServerHomeDir(agentDir: string): string {
|
||||
return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
|
||||
}
|
||||
export { resolveCodexAppServerHomeDir } from "./auth-start-options.js";
|
||||
|
||||
async function withCodexHomeEnvironment(
|
||||
startOptions: CodexAppServerStartOptions,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import path from "node:path";
|
||||
import type { CodexAppServerStartOptions } from "./config.js";
|
||||
|
||||
const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
|
||||
const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = 'cli_auth_credentials_store="ephemeral"';
|
||||
|
||||
export function resolveCodexAppServerHomeDir(agentDir: string): string {
|
||||
return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
|
||||
}
|
||||
|
||||
/** Forces OpenClaw-owned Codex auth to remain process-local. */
|
||||
export function withEphemeralCodexAuthStore(params: {
|
||||
startOptions: CodexAppServerStartOptions;
|
||||
preparedAuth?: unknown;
|
||||
authProfileId?: string | null;
|
||||
}): CodexAppServerStartOptions {
|
||||
const { startOptions } = params;
|
||||
const managedCodexCli =
|
||||
startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed";
|
||||
if (!managedCodexCli || (!params.preparedAuth && params.authProfileId === null)) {
|
||||
return startOptions;
|
||||
}
|
||||
if (
|
||||
startOptions.args.at(-2) === "-c" &&
|
||||
startOptions.args.at(-1) === CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE
|
||||
) {
|
||||
return startOptions;
|
||||
}
|
||||
return {
|
||||
...startOptions,
|
||||
args: [...startOptions.args, "-c", CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE],
|
||||
};
|
||||
}
|
||||
@@ -1663,6 +1663,90 @@ describe("runEmbeddedAgent overflow compaction trigger routing", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("binds a harness-owned SecretRef to its exact runtime when auth stays opaque", async () => {
|
||||
const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js");
|
||||
const runtimeArtifact = {
|
||||
id: "codex-app-server:test",
|
||||
fingerprint: "codex-runtime-fingerprint",
|
||||
};
|
||||
const pluginRunAttempt = vi.fn<AgentHarness["runAttempt"]>(async () =>
|
||||
makeAttemptResult({
|
||||
assistantTexts: ["ok"],
|
||||
runtimeArtifact,
|
||||
}),
|
||||
);
|
||||
const codexAuthStore = {
|
||||
version: 1 as const,
|
||||
profiles: {
|
||||
"openai:work": {
|
||||
type: "api_key" as const,
|
||||
provider: "openai",
|
||||
keyRef: { source: "env" as const, provider: "default", id: "OPENAI_WORK_KEY" },
|
||||
},
|
||||
},
|
||||
};
|
||||
const onSuccessfulAuthBinding = vi.fn();
|
||||
clearAgentHarnesses();
|
||||
registerAgentHarness({
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
supports: codexHarnessSupportsKnownProviders,
|
||||
authBootstrap: "harness",
|
||||
runtimeArtifact: { validate: vi.fn(async () => true) },
|
||||
runAttempt: pluginRunAttempt,
|
||||
});
|
||||
mockedEnsureAuthProfileStore.mockReturnValueOnce(codexAuthStore);
|
||||
mockedEnsureAuthProfileStoreWithoutExternalProfiles.mockReturnValueOnce(codexAuthStore);
|
||||
mockedResolveModelAsync.mockResolvedValueOnce({
|
||||
model: {
|
||||
id: "gpt-5.4",
|
||||
provider: "openai",
|
||||
contextWindow: 200000,
|
||||
api: "openai-chatgpt-responses",
|
||||
},
|
||||
error: null,
|
||||
authStorage: { setRuntimeApiKey: vi.fn() },
|
||||
modelRegistry: {},
|
||||
});
|
||||
|
||||
try {
|
||||
await runEmbeddedAgent({
|
||||
...overflowBaseRunParams,
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
config: {
|
||||
agents: { defaults: { agentRuntime: { id: "codex" } } },
|
||||
auth: {
|
||||
profiles: {
|
||||
"openai:work": { provider: "openai", mode: "api_key" },
|
||||
},
|
||||
},
|
||||
},
|
||||
authProfileId: "openai:work",
|
||||
authProfileIdSource: "user",
|
||||
runId: "harness-secretref-runtime-owner-binding",
|
||||
onSuccessfulAuthBinding,
|
||||
} as RunEmbeddedAgentParams & {
|
||||
onSuccessfulAuthBinding: typeof onSuccessfulAuthBinding;
|
||||
});
|
||||
} finally {
|
||||
clearAgentHarnesses();
|
||||
}
|
||||
|
||||
expect(pluginRunAttempt).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ captureRuntimeArtifact: true }),
|
||||
);
|
||||
expect(onSuccessfulAuthBinding).toHaveBeenCalledWith({
|
||||
authProfileId: "openai:work",
|
||||
agentHarnessId: "codex",
|
||||
runtimeOwnerFingerprint: expect.any(String),
|
||||
runtimeOwnerKind: "plugin-harness",
|
||||
runtimeOwnerId: "codex",
|
||||
runtimeArtifactId: runtimeArtifact.id,
|
||||
runtimeArtifactFingerprint: runtimeArtifact.fingerprint,
|
||||
});
|
||||
});
|
||||
|
||||
it("bootstraps OAuth credentials for forced openai/* Codex response runs", async () => {
|
||||
const { clearAgentHarnesses, registerAgentHarness } = await import("../harness/registry.js");
|
||||
const pluginRunAttempt = vi.fn<AgentHarness["runAttempt"]>(async () =>
|
||||
|
||||
@@ -4181,8 +4181,7 @@ async function runEmbeddedAgentInternal(
|
||||
? fingerprintResolvedProviderAuth(successfulApiKeyInfo)
|
||||
: undefined;
|
||||
const authProfileOwnerFingerprint =
|
||||
successfulProfileId &&
|
||||
(!pluginHarnessOwnsTransport || successfulCredential?.type === "oauth")
|
||||
successfulProfileId && successfulCredential !== undefined
|
||||
? fingerprintAuthProfileOwnerShape({
|
||||
profileId: successfulProfileId,
|
||||
credential: successfulCredential,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
const UNCONFIGURED_CONFIG_IGNORED_KEYS = new Set(["$schema", "meta"]);
|
||||
|
||||
function isIncompleteWizardConfig(value: unknown): boolean {
|
||||
return (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
Object.keys(value).every((key) => key === "securityAcknowledgedAt")
|
||||
);
|
||||
}
|
||||
|
||||
export function isUnconfiguredConfigSource(sourceConfig: Record<string, unknown>): boolean {
|
||||
return Object.entries(sourceConfig).every(
|
||||
([key, value]) =>
|
||||
UNCONFIGURED_CONFIG_IGNORED_KEYS.has(key) ||
|
||||
(key === "wizard" && isIncompleteWizardConfig(value)),
|
||||
);
|
||||
}
|
||||
@@ -2713,6 +2713,25 @@ describe("runCli exit behavior", () => {
|
||||
expect(buildProgramMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resumes onboarding when an interrupted first run only persisted risk acknowledgement", async () => {
|
||||
readConfigFileSnapshotMock.mockResolvedValueOnce({
|
||||
exists: true,
|
||||
valid: true,
|
||||
sourceConfig: {
|
||||
meta: { updatedBy: "fixture" },
|
||||
wizard: { securityAcknowledgedAt: "2026-07-13T00:00:00.000Z" },
|
||||
},
|
||||
});
|
||||
|
||||
await withInteractiveTty(async () => {
|
||||
await runCli(["node", "openclaw"]);
|
||||
});
|
||||
|
||||
expect(setupWizardCommandMock).toHaveBeenCalledWith({});
|
||||
expect(tryRouteCliMock).not.toHaveBeenCalled();
|
||||
expect(buildProgramMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("points noninteractive fresh bare root invocations to onboarding automation", async () => {
|
||||
const previousExitCode = process.exitCode;
|
||||
const stdinDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");
|
||||
|
||||
+2
-5
@@ -27,6 +27,7 @@ import {
|
||||
shouldSkipPluginCommandRegistration,
|
||||
} from "./command-registration-policy.js";
|
||||
import { maybeRunCliInContainer, parseCliContainerArgs } from "./container-target.js";
|
||||
import { isUnconfiguredConfigSource } from "./fresh-install-config.js";
|
||||
import {
|
||||
consumeGatewayFastPathRootOptionToken,
|
||||
consumeGatewayRunOptionToken,
|
||||
@@ -269,8 +270,6 @@ async function disposeCliAgentHarnesses(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const UNCONFIGURED_CONFIG_IGNORED_KEYS = new Set(["$schema", "meta"]);
|
||||
|
||||
function isUnconfiguredConfigSnapshot(
|
||||
snapshot: Pick<ConfigFileSnapshot, "exists" | "valid" | "sourceConfig">,
|
||||
): boolean {
|
||||
@@ -280,9 +279,7 @@ function isUnconfiguredConfigSnapshot(
|
||||
if (!snapshot.valid) {
|
||||
return false;
|
||||
}
|
||||
return Object.keys(snapshot.sourceConfig).every((key) =>
|
||||
UNCONFIGURED_CONFIG_IGNORED_KEYS.has(key),
|
||||
);
|
||||
return isUnconfiguredConfigSource(snapshot.sourceConfig);
|
||||
}
|
||||
|
||||
export async function shouldStartOnboardingForFreshInstall(argv: string[]): Promise<boolean> {
|
||||
|
||||
@@ -157,6 +157,29 @@ describe("Codex runtime plugin install repair", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows source checkouts to use the matching bundled Codex plugin", async () => {
|
||||
const { ensureCodexRuntimePluginForModelSelection } =
|
||||
await import("./codex-runtime-plugin-install.js");
|
||||
|
||||
await ensureCodexRuntimePluginForModelSelection({
|
||||
cfg: {},
|
||||
model: "openai/gpt-5.5",
|
||||
prompter: {} as never,
|
||||
runtime: {} as never,
|
||||
});
|
||||
|
||||
expect(mocks.ensureOnboardingPluginInstalled).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
entry: {
|
||||
pluginId: "codex",
|
||||
label: "Codex",
|
||||
install: { npmSpec: "@openclaw/codex", defaultChoice: "npm" },
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("sees an agent-scoped Codex runtime pin behind a custom OpenAI route", async () => {
|
||||
mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue({
|
||||
codex: { source: "npm", installPath: process.cwd() },
|
||||
|
||||
@@ -103,6 +103,7 @@ function setupDeps(params: {
|
||||
detect?: GuidedOnboardingDeps["detect"];
|
||||
activate?: GuidedOnboardingDeps["activate"];
|
||||
runCrestodianChat?: GuidedOnboardingDeps["runCrestodianChat"];
|
||||
persistRiskAcknowledgement?: GuidedOnboardingDeps["persistRiskAcknowledgement"];
|
||||
}) {
|
||||
const runCrestodianChat = vi.fn<NonNullable<GuidedOnboardingDeps["runCrestodianChat"]>>(
|
||||
params.runCrestodianChat ?? (async () => {}),
|
||||
@@ -118,6 +119,7 @@ function setupDeps(params: {
|
||||
latencyMs: 1250,
|
||||
lines: ["Workspace: /tmp/work", "Gateway: running"],
|
||||
})),
|
||||
persistRiskAcknowledgement: params.persistRiskAcknowledgement ?? vi.fn(async () => undefined),
|
||||
runCrestodianChat,
|
||||
} satisfies GuidedOnboardingDeps;
|
||||
}
|
||||
@@ -178,6 +180,22 @@ describe("runGuidedOnboarding", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("persists the one-time risk acknowledgement before inference detection", async () => {
|
||||
const prompter = createWizardPrompter();
|
||||
const persistRiskAcknowledgement = vi.fn(async () => undefined);
|
||||
const detect = vi.fn(async () => detection());
|
||||
const deps = setupDeps({ prompter, persistRiskAcknowledgement, detect });
|
||||
|
||||
await runGuidedOnboarding({ acceptRisk: true }, makeRuntime(), deps);
|
||||
|
||||
expect(persistRiskAcknowledgement).toHaveBeenCalledWith({
|
||||
wizard: { securityAcknowledgedAt: expect.any(String) },
|
||||
});
|
||||
expect(persistRiskAcknowledgement.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
detect.mock.invocationCallOrder[0]!,
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the configured workspace only as inference and Crestodian context", async () => {
|
||||
readConfigFileSnapshot.mockResolvedValueOnce({
|
||||
exists: true,
|
||||
|
||||
@@ -34,6 +34,7 @@ export type GuidedOnboardingDeps = {
|
||||
acceptRisk: boolean,
|
||||
) => Promise<void>;
|
||||
createPrompter?: () => WizardPrompter | Promise<WizardPrompter>;
|
||||
persistRiskAcknowledgement?: (config: OpenClawConfig) => Promise<void>;
|
||||
};
|
||||
|
||||
type GuidedOnboardingHandoff = { workspace: string };
|
||||
@@ -270,6 +271,22 @@ function activationLines(result: Extract<ActivateSetupInferenceResult, { ok: tru
|
||||
];
|
||||
}
|
||||
|
||||
async function persistRiskAcknowledgement(config: OpenClawConfig): Promise<void> {
|
||||
const securityAcknowledgedAt = config.wizard?.securityAcknowledgedAt;
|
||||
if (!securityAcknowledgedAt) {
|
||||
return;
|
||||
}
|
||||
const { mutateConfigFileWithRetry } = await import("../config/config.js");
|
||||
await mutateConfigFileWithRetry({
|
||||
mutate: (draft) => {
|
||||
if (draft.wizard?.securityAcknowledgedAt) {
|
||||
return;
|
||||
}
|
||||
draft.wizard = { ...draft.wizard, securityAcknowledgedAt };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function runGuidedOnboardingFlow(
|
||||
opts: OnboardOptions,
|
||||
runtime: RuntimeEnv,
|
||||
@@ -307,14 +324,21 @@ async function runGuidedOnboardingFlow(
|
||||
}
|
||||
const existingConfig =
|
||||
snapshot.exists && snapshot.valid ? (snapshot.sourceConfig ?? snapshot.config) : {};
|
||||
await requireRiskAcknowledgement({ opts, prompter, config: existingConfig });
|
||||
const acknowledgedConfig = await requireRiskAcknowledgement({
|
||||
opts,
|
||||
prompter,
|
||||
config: existingConfig,
|
||||
});
|
||||
if (!existingConfig.wizard?.securityAcknowledgedAt) {
|
||||
await (deps.persistRiskAcknowledgement ?? persistRiskAcknowledgement)(acknowledgedConfig);
|
||||
}
|
||||
|
||||
// Inference is the only prerequisite for Crestodian. Use the caller's or
|
||||
// current default workspace as isolated probe context; Crestodian owns any
|
||||
// workspace choice and persistence after the live completion succeeds.
|
||||
const workspace = resolveUserPath(
|
||||
opts.workspace?.trim() ||
|
||||
existingConfig.agents?.defaults?.workspace?.trim() ||
|
||||
acknowledgedConfig.agents?.defaults?.workspace?.trim() ||
|
||||
onboardHelpers.DEFAULT_WORKSPACE,
|
||||
);
|
||||
|
||||
|
||||
@@ -139,7 +139,6 @@ async function ensureRuntimePluginForModelSelection(params: {
|
||||
defaultChoice: "npm",
|
||||
},
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
preferRemoteInstall: true,
|
||||
},
|
||||
prompter: params.prompter,
|
||||
runtime: params.runtime,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
const SETUP_INFERENCE_TEST_MAX_TOKENS = 32;
|
||||
|
||||
/** Plugin and auto-selected harnesses may not support OpenClaw's request-scoped token cap. */
|
||||
export function resolveSetupInferenceProbeStreamParams(agentHarnessId?: string): {
|
||||
streamParams?: { maxTokens: number };
|
||||
} {
|
||||
return !agentHarnessId || agentHarnessId === "openclaw"
|
||||
? { streamParams: { maxTokens: SETUP_INFERENCE_TEST_MAX_TOKENS } }
|
||||
: {};
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from "./agent-turn.js";
|
||||
import { resolveCrestodianConfiguredRouteFromConfig } from "./inference-route.js";
|
||||
import { applyCrestodianModelSelection } from "./setup-apply.js";
|
||||
import { resolveSetupInferenceProbeStreamParams } from "./setup-inference-probe.js";
|
||||
import {
|
||||
SetupInferenceActivationIndeterminateError,
|
||||
activateSetupInference as activateSetupInferenceImpl,
|
||||
@@ -572,6 +573,13 @@ async function runCodexSetupWithFinalConfig(params: {
|
||||
}
|
||||
|
||||
describe("activateSetupInference", () => {
|
||||
it("omits the token cap when harness selection is automatic", () => {
|
||||
expect(resolveSetupInferenceProbeStreamParams("auto")).toEqual({});
|
||||
expect(resolveSetupInferenceProbeStreamParams("openclaw")).toEqual({
|
||||
streamParams: { maxTokens: 32 },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.appendAudit.mockReset();
|
||||
mocks.ensureSelectedAgentHarnessPlugin.mockReset().mockResolvedValue(undefined);
|
||||
@@ -3390,6 +3398,7 @@ describe("activateSetupInference", () => {
|
||||
expect(runEmbeddedAgent.mock.calls[0]?.[0]).toMatchObject({
|
||||
agentHarnessRuntimeOverride: "codex",
|
||||
});
|
||||
expect(runEmbeddedAgent.mock.calls[0]?.[0]).not.toHaveProperty("streamParams");
|
||||
expect(persistedConfig).toMatchObject({
|
||||
gateway: { port: 19000 },
|
||||
models: {
|
||||
|
||||
@@ -74,6 +74,7 @@ import {
|
||||
createCrestodianModelSelectionUpdater,
|
||||
createQuickstartNotePrompter,
|
||||
} from "./setup-apply.js";
|
||||
import { resolveSetupInferenceProbeStreamParams } from "./setup-inference-probe.js";
|
||||
import {
|
||||
captureCrestodianOwnerPluginArtifacts,
|
||||
createCrestodianVerifiedInferenceBinding,
|
||||
@@ -95,7 +96,6 @@ const log = createSubsystemLogger("crestodian/setup-inference");
|
||||
*/
|
||||
export const SETUP_INFERENCE_TEST_TIMEOUT_MS = 90_000;
|
||||
const SETUP_INFERENCE_TEST_PROMPT = "Reply with the single word OK. Do not use tools.";
|
||||
const SETUP_INFERENCE_TEST_MAX_TOKENS = 32;
|
||||
|
||||
export type SetupInferenceCandidate = {
|
||||
kind: InferenceBackendKind;
|
||||
@@ -2880,7 +2880,7 @@ async function runSetupInferenceTest(params: {
|
||||
thinkLevel: "off",
|
||||
reasoningLevel: "off",
|
||||
verboseLevel: "off",
|
||||
streamParams: { maxTokens: SETUP_INFERENCE_TEST_MAX_TOKENS },
|
||||
...resolveSetupInferenceProbeStreamParams(plan.agentHarnessRuntimeOverride),
|
||||
disableTools: true,
|
||||
modelRun: true,
|
||||
messageChannel: "crestodian",
|
||||
|
||||
Reference in New Issue
Block a user