Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8904,6 +8904,8 @@ const onboardRuntimeBoundary = new OnboardRuntimeBoundary({
const startRecordedStep = onboardRuntimeBoundary.startRecordedStep.bind(onboardRuntimeBoundary);
const recordStepComplete = onboardRuntimeBoundary.recordStepComplete.bind(onboardRuntimeBoundary);
const recordStepSkipped = onboardRuntimeBoundary.recordStepSkipped.bind(onboardRuntimeBoundary);
const recordStateSkipped = onboardRuntimeBoundary.recordStateSkipped.bind(onboardRuntimeBoundary);
const recordRepairEvent = onboardRuntimeBoundary.recordRepairEvent.bind(onboardRuntimeBoundary);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
const recordStepFailed = onboardRuntimeBoundary.recordStepFailed.bind(onboardRuntimeBoundary);
const recordSessionComplete = onboardRuntimeBoundary.recordSessionComplete.bind(onboardRuntimeBoundary);

Expand Down Expand Up @@ -9293,6 +9295,7 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> {
resolveSandboxGpuConfig,
validateSandboxGpuPreflight,
skippedStepMessage,
recordStateSkipped,
startRecordedStep,
recordStepComplete,
updateSession: onboardSession.updateSession,
Expand Down Expand Up @@ -9361,6 +9364,7 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> {
retireLegacyGatewayForDockerDriverUpgrade,
destroyGatewayRuntimeForGpuReuse: () => destroyGateway(() => undefined, () => false),
skippedStepMessage,
recordStateSkipped,
note,
startRecordedStep,
startGateway,
Expand Down Expand Up @@ -9417,6 +9421,8 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> {
toSessionUpdates: (updates) => toSessionUpdates(updates as Parameters<typeof toSessionUpdates>[0]),
skippedStepMessage,
ensureResumeProviderReady,
recordStateSkipped,
recordRepairEvent,
hydrateCredentialEnv,
repairLocalInferenceSystemdOverrideOrExit,
isNonInteractive,
Expand Down Expand Up @@ -9511,6 +9517,8 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> {
recordStepComplete,
toSessionUpdates: (updates) => toSessionUpdates(updates as Parameters<typeof toSessionUpdates>[0]),
skippedStepMessage,
recordStateSkipped,
recordRepairEvent,
error: (message) => console.error(message),
exitProcess: (code) => process.exit(code),
},
Expand Down Expand Up @@ -9546,6 +9554,7 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> {
recordStepSkipped,
isOpenclawReady,
skippedStepMessage,
recordStateSkipped,
startRecordedStep,
setupOpenclaw,
syncNemoClawConfigInSandbox,
Expand Down Expand Up @@ -9581,6 +9590,7 @@ async function onboard(opts: OnboardOptions = {}): Promise<void> {
preparePolicyPresetResumeSelection({ policies }, name, options),
arePolicyPresetsApplied,
skippedStepMessage,
recordStateSkipped,
startRecordedStep,
setupPoliciesWithSelection,
updateSession: onboardSession.updateSession,
Expand Down
6 changes: 6 additions & 0 deletions src/lib/onboard/machine/handlers/agent-setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function createDeps(overrides: Partial<AgentSetupStateOptions<Agent>["deps"]> =
}),
openclawReady: vi.fn(() => false),
skippedMessage: vi.fn(),
recordSkip: vi.fn(async () => createSession()),
startStep: vi.fn(async () => undefined),
setupOpenclaw: vi.fn(async () => undefined),
syncConfig: vi.fn(),
Expand All @@ -38,6 +39,7 @@ function createDeps(overrides: Partial<AgentSetupStateOptions<Agent>["deps"]> =
recordStepSkipped: calls.skipped,
isOpenclawReady: calls.openclawReady,
skippedStepMessage: calls.skippedMessage,
recordStateSkipped: calls.recordSkip,
startRecordedStep: calls.startStep,
setupOpenclaw: calls.setupOpenclaw,
syncNemoClawConfigInSandbox: calls.syncConfig,
Expand Down Expand Up @@ -94,6 +96,10 @@ describe("handleAgentSetupState", () => {
const result = await handleAgentSetupState({ ...baseOptions(deps), resume: true });

expect(calls.skippedMessage).toHaveBeenCalledWith("openclaw", "my-assistant");
expect(calls.recordSkip).toHaveBeenCalledWith("openclaw", {
reason: "resume",
sandboxName: "my-assistant",
});
expect(calls.startStep).not.toHaveBeenCalled();
expect(calls.setupOpenclaw).not.toHaveBeenCalled();
expect(calls.syncConfig).toHaveBeenCalledWith("my-assistant", "provider", "model");
Expand Down
4 changes: 3 additions & 1 deletion src/lib/onboard/machine/handlers/agent-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
recordStepSkipped(stepName: string): Promise<Session>;
isOpenclawReady(sandboxName: string): boolean;
skippedStepMessage(stepName: string, detail?: string | null): void;
recordStateSkipped(state: "openclaw", metadata?: Record<string, unknown> | null): Promise<Session>;
startRecordedStep(
stepName: string,
updates: { sandboxName: string; provider: string; model: string },
Expand Down Expand Up @@ -72,10 +73,11 @@
if (resumeOpenclaw) {
deps.skippedStepMessage("openclaw", sandboxName);
deps.syncNemoClawConfigInSandbox(sandboxName, provider, model);
await deps.recordStepComplete(
await deps.recordStateSkipped("openclaw", { reason: "resume", sandboxName });
session = await deps.recordStepComplete(
"openclaw",
deps.toSessionUpdates({ sandboxName, provider, model, hermesAuthMethod, hermesToolGateways }),
);

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

The value assigned to session here is unused.
} else {
await deps.startRecordedStep("openclaw", { sandboxName, provider, model });
await deps.setupOpenclaw(sandboxName, model, provider);
Expand Down
10 changes: 10 additions & 0 deletions src/lib/onboard/machine/handlers/gateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function createDeps(overrides: Partial<GatewayStateOptions<Gpu>["deps"]> = {}) {
retireLegacy: vi.fn(),
destroyGpuRuntime: vi.fn(() => true),
skipped: vi.fn(),
recordSkip: vi.fn(async () => createSession()),
note: vi.fn(),
startStep: vi.fn(async () => undefined),
startGateway: vi.fn(async () => undefined),
Expand All @@ -52,6 +53,7 @@ function createDeps(overrides: Partial<GatewayStateOptions<Gpu>["deps"]> = {}) {
retireLegacyGatewayForDockerDriverUpgrade: calls.retireLegacy,
destroyGatewayRuntimeForGpuReuse: calls.destroyGpuRuntime,
skippedStepMessage: calls.skipped,
recordStateSkipped: calls.recordSkip,
note: calls.note,
startRecordedStep: calls.startStep,
startGateway: calls.startGateway,
Expand Down Expand Up @@ -99,6 +101,10 @@ describe("handleGatewayState", () => {
await handleGatewayState(baseOptions(deps, "healthy"));

expect(calls.skipped).toHaveBeenCalledWith("gateway", "running", "reuse");
expect(calls.recordSkip).toHaveBeenCalledWith("gateway", {
reason: "reuse",
reuseState: "healthy",
});
expect(calls.note).toHaveBeenCalledWith(" Reusing healthy NemoClaw gateway.");
expect(calls.startGateway).not.toHaveBeenCalled();
expect(calls.complete).toHaveBeenCalledWith("gateway");
Expand All @@ -112,6 +118,10 @@ describe("handleGatewayState", () => {
await handleGatewayState({ ...baseOptions(deps, "healthy", session), resume: true });

expect(calls.skipped).toHaveBeenCalledWith("gateway", "running");
expect(calls.recordSkip).toHaveBeenCalledWith("gateway", {
reason: "resume",
reuseState: "healthy",
});
expect(calls.startGateway).not.toHaveBeenCalled();
});

Expand Down
3 changes: 3 additions & 0 deletions src/lib/onboard/machine/handlers/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface GatewayStateOptions<Gpu> {
detail?: string | null,
reason?: "resume" | "reuse",
): void;
recordStateSkipped(state: "gateway", metadata?: Record<string, unknown> | null): Promise<Session>;
note(message: string): void;
startRecordedStep(stepName: string): Promise<void>;
startGateway(gpu: Gpu, options: { gpuPassthrough: boolean }): Promise<void>;
Expand Down Expand Up @@ -150,9 +151,11 @@ export async function handleGatewayState<Gpu>({
const resumeGateway = resume && session?.steps?.gateway?.status === "complete" && canReuseHealthyGateway;
if (resumeGateway) {
deps.skippedStepMessage("gateway", "running");
await deps.recordStateSkipped("gateway", { reason: "resume", reuseState: gatewayReuseState });
session = await deps.recordStepComplete("gateway");
} else if (!resume && canReuseHealthyGateway) {
deps.skippedStepMessage("gateway", "running", "reuse");
await deps.recordStateSkipped("gateway", { reason: "reuse", reuseState: gatewayReuseState });
deps.note(" Reusing healthy NemoClaw gateway.");
session = await deps.recordStepComplete("gateway");
} else {
Expand Down
6 changes: 6 additions & 0 deletions src/lib/onboard/machine/handlers/policies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function createDeps(overrides: Partial<PoliciesStateOptions<Agent, WebSearchConf
),
appliedCheck: vi.fn(() => false),
skipped: vi.fn(),
recordSkip: vi.fn(async () => session),
startStep: vi.fn(async () => undefined),
setupPolicies: vi.fn(async () => ["npm"]),
updateSession: vi.fn((mutator: (value: Session) => Session | void) => {
Expand All @@ -52,6 +53,7 @@ function createDeps(overrides: Partial<PoliciesStateOptions<Agent, WebSearchConf
preparePolicyPresetResumeSelection: calls.prepareResume,
arePolicyPresetsApplied: calls.appliedCheck,
skippedStepMessage: calls.skipped,
recordStateSkipped: calls.recordSkip,
startRecordedStep: calls.startStep,
setupPoliciesWithSelection: calls.setupPolicies,
updateSession: calls.updateSession,
Expand Down Expand Up @@ -146,6 +148,10 @@ describe("handlePoliciesState", () => {
const result = await handlePoliciesState({ ...baseOptions(deps), resume: true });

expect(calls.skipped).toHaveBeenCalledWith("policies", "npm");
expect(calls.recordSkip).toHaveBeenCalledWith("policies", {
reason: "resume",
policyPresets: ["npm"],
});
expect(calls.setupPolicies).not.toHaveBeenCalled();
expect(calls.complete).toHaveBeenCalledWith(
"policies",
Expand Down
5 changes: 5 additions & 0 deletions src/lib/onboard/machine/handlers/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface PoliciesStateOptions<Agent, WebSearchConfig> {
): PolicyResumeSelection;
arePolicyPresetsApplied(sandboxName: string, selectedPresets: string[]): boolean;
skippedStepMessage(stepName: string, detail?: string | null): void;
recordStateSkipped(state: "policies", metadata?: Record<string, unknown> | null): Promise<Session>;
startRecordedStep(
stepName: string,
updates: { sandboxName: string; provider: string; model: string; policyPresets: string[] },
Expand Down Expand Up @@ -148,6 +149,10 @@ export async function handlePoliciesState<Agent, WebSearchConfig>({
let session: Session | null;
if (resumePolicies) {
deps.skippedStepMessage("policies", recordedPolicyPresetsForSupport.join(", "));
await deps.recordStateSkipped("policies", {
reason: "resume",
policyPresets: recordedPolicyPresetsForSupport,
});
session = await deps.recordStepComplete(
"policies",
deps.toSessionUpdates({
Expand Down
5 changes: 5 additions & 0 deletions src/lib/onboard/machine/handlers/preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function createDeps(overrides: Partial<PreflightStateOptions<Gpu, SandboxEntry,
}),
validateSandboxGpuPreflight: vi.fn(),
skippedStepMessage: vi.fn(),
recordStateSkipped: vi.fn(async () => session),
startRecordedStep: vi.fn(async () => undefined),
recordStepComplete: vi.fn(async () => session),
updateSession: vi.fn((mutator: (value: Session) => Session | void) => {
Expand Down Expand Up @@ -125,6 +126,10 @@ describe("handlePreflightState", () => {
});

expect(harness.deps.skippedStepMessage).toHaveBeenCalledWith("preflight", "cached");
expect(harness.deps.recordStateSkipped).toHaveBeenCalledWith("preflight", {
reason: "resume",
validation: "gpu-cdi",
});
expect(harness.deps.detectGpu).toHaveBeenCalledOnce();
expect(harness.deps.runPreflight).not.toHaveBeenCalled();
expect(harness.deps.startRecordedStep).not.toHaveBeenCalled();
Expand Down
2 changes: 2 additions & 0 deletions src/lib/onboard/machine/handlers/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface PreflightStateOptions<
): Config;
validateSandboxGpuPreflight(config: Config): void;
skippedStepMessage(stepName: string, detail?: string | null): void;
recordStateSkipped(state: "preflight", metadata?: Record<string, unknown> | null): Promise<Session>;
startRecordedStep(stepName: string): Promise<void>;
recordStepComplete(stepName: string): Promise<Session>;
updateSession(mutator: (session: Session) => Session | void): Session;
Expand Down Expand Up @@ -111,6 +112,7 @@ export async function handlePreflightState<
let gpu: Gpu;
if (resumePreflight) {
deps.skippedStepMessage("preflight", "cached");
await deps.recordStateSkipped("preflight", { reason: "resume", validation: "gpu-cdi" });
gpu = deps.detectGpu();
const resumeSandboxGpuConfig = deps.resolveSandboxGpuConfig(gpu, {
flag: effectiveSandboxGpuFlag,
Expand Down
22 changes: 22 additions & 0 deletions src/lib/onboard/machine/handlers/provider-inference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ function createDeps(overrides: Partial<ProviderInferenceStateOptions<Gpu, Agent,
forceInferenceSetup: false,
credentialEnv: credentialEnv ?? null,
})),
recordSkip: vi.fn(async () => createSession()),
repairEvent: vi.fn(async () => createSession()),
hydrate: vi.fn(),
repair: vi.fn(),
routeReady: vi.fn(() => false),
Expand All @@ -61,6 +63,8 @@ function createDeps(overrides: Partial<ProviderInferenceStateOptions<Gpu, Agent,
toSessionUpdates: (updates: Record<string, unknown>) => updates as SessionUpdates,
skippedStepMessage: calls.skipped,
ensureResumeProviderReady: calls.recoverProvider,
recordStateSkipped: calls.recordSkip,
recordRepairEvent: calls.repairEvent,
hydrateCredentialEnv: calls.hydrate,
repairLocalInferenceSystemdOverrideOrExit: calls.repair,
isNonInteractive: () => true,
Expand Down Expand Up @@ -218,9 +222,27 @@ describe("handleProviderInferenceState", () => {
expect(calls.setupInference).not.toHaveBeenCalled();
expect(calls.recoverProvider).toHaveBeenCalledWith("ollama-local", null);
expect(calls.skipped).toHaveBeenCalledWith("provider_selection", "ollama-local / llama3.1");
expect(calls.recordSkip).toHaveBeenCalledWith("provider_selection", {
reason: "resume",
provider: "ollama-local",
model: "llama3.1",
});
expect(calls.hydrate).toHaveBeenCalledWith(null);
expect(calls.repairEvent).toHaveBeenCalledWith("state.repair.started", {
state: "provider_selection",
metadata: { repair: "ollama-systemd-loopback" },
});
expect(calls.repair).toHaveBeenCalledWith("ollama-local", deps.isNonInteractive);
expect(calls.repairEvent).toHaveBeenCalledWith("state.repair.completed", {
state: "provider_selection",
metadata: { repair: "ollama-systemd-loopback" },
});
expect(calls.skipped).toHaveBeenCalledWith("inference", "ollama-local / llama3.1");
expect(calls.recordSkip).toHaveBeenCalledWith("inference", {
reason: "resume",
provider: "ollama-local",
model: "llama3.1",
});
expect(result).toMatchObject({ provider: "ollama-local", model: "llama3.1" });
});

Expand Down
30 changes: 30 additions & 0 deletions src/lib/onboard/machine/handlers/provider-inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ export interface ProviderInferenceStateOptions<Gpu, Agent, Host> {
provider: string | null | undefined,
credentialEnv: string | null | undefined,
): Promise<{ forceInferenceSetup: boolean; credentialEnv: string | null }>;
recordStateSkipped(
state: "provider_selection" | "inference",
metadata?: Record<string, unknown> | null,
): Promise<Session>;
recordRepairEvent(
type: "state.repair.started" | "state.repair.completed" | "state.repair.failed",
options?: { state?: "provider_selection" | "inference"; error?: string | null; metadata?: Record<string, unknown> | null },
): Promise<Session>;
hydrateCredentialEnv(credentialEnv: string | null): void;
repairLocalInferenceSystemdOverrideOrExit(provider: string | null, isNonInteractive: () => boolean): void;
isNonInteractive(): boolean;
Expand Down Expand Up @@ -172,8 +180,25 @@ export async function handleProviderInferenceState<Gpu, Agent, Host>({
forceInferenceSetup = recovery.forceInferenceSetup;
credentialEnv = recovery.credentialEnv;
deps.skippedStepMessage("provider_selection", `${provider} / ${model}`);
await deps.recordStateSkipped("provider_selection", {
reason: "resume",
provider,
model,
});
deps.hydrateCredentialEnv(credentialEnv);
if (provider === "ollama-local") {
await deps.recordRepairEvent("state.repair.started", {
state: "provider_selection",
metadata: { repair: "ollama-systemd-loopback" },
});
}
deps.repairLocalInferenceSystemdOverrideOrExit(provider, deps.isNonInteractive);
if (provider === "ollama-local") {
await deps.recordRepairEvent("state.repair.completed", {
state: "provider_selection",
metadata: { repair: "ollama-systemd-loopback" },
});
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
await deps.startRecordedStep("provider_selection");
const selection = await deps.setupNim(gpu, sandboxName, agent);
Expand Down Expand Up @@ -251,6 +276,11 @@ export async function handleProviderInferenceState<Gpu, Agent, Host>({
}
}
deps.skippedStepMessage("inference", `${provider} / ${model}`);
await deps.recordStateSkipped("inference", {
reason: "resume",
provider,
model,
});
if (nimContainer && sandboxName) deps.registryUpdateSandbox(sandboxName, { nimContainer });
session = await deps.recordStepComplete(
"inference",
Expand Down
16 changes: 16 additions & 0 deletions src/lib/onboard/machine/handlers/sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ function createDeps(overrides: Partial<SandboxStateOptions<Gpu, Agent, WebSearch
setDefault: vi.fn(),
complete: vi.fn(async () => createSession()),
skipped: vi.fn(),
recordSkip: vi.fn(async () => createSession()),
repairEvent: vi.fn(async () => createSession()),
error: vi.fn(),
exit: vi.fn((code: number): never => {
throw new Error(`exit ${code}`);
Expand Down Expand Up @@ -79,6 +81,8 @@ function createDeps(overrides: Partial<SandboxStateOptions<Gpu, Agent, WebSearch
recordStepComplete: calls.complete,
toSessionUpdates: (updates: Record<string, unknown>) => updates as SessionUpdates,
skippedStepMessage: calls.skipped,
recordStateSkipped: calls.recordSkip,
recordRepairEvent: calls.repairEvent,
error: calls.error,
exitProcess: calls.exit,
...overrides,
Expand Down Expand Up @@ -156,6 +160,10 @@ describe("handleSandboxState", () => {

expect(calls.createSandbox).not.toHaveBeenCalled();
expect(calls.skipped).toHaveBeenCalledWith("sandbox", "saved");
expect(calls.recordSkip).toHaveBeenCalledWith("sandbox", {
reason: "resume",
sandboxName: "saved",
});
expect(result.selectedMessagingChannels).toEqual(["slack"]);
});

Expand Down Expand Up @@ -185,7 +193,15 @@ describe("handleSandboxState", () => {

await handleSandboxState({ ...baseOptions(deps, session), resume: true, sandboxName: "saved" });

expect(calls.repairEvent).toHaveBeenCalledWith("state.repair.started", {
state: "sandbox",
metadata: { repair: "recorded-sandbox-cleanup", sandboxName: "saved" },
});
expect(calls.repairSandbox).toHaveBeenCalledWith("saved");
expect(calls.repairEvent).toHaveBeenCalledWith("state.repair.completed", {
state: "sandbox",
metadata: { repair: "recorded-sandbox-cleanup", sandboxName: "saved" },
});
expect(calls.createSandbox).toHaveBeenCalled();
});

Expand Down
Loading
Loading