Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
112 changes: 28 additions & 84 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
const { OnboardRuntimeBoundary }: typeof import("./onboard/runtime-boundary") = require("./onboard/runtime-boundary");
const { handleAgentSetupState }: typeof import("./onboard/machine/handlers/agent-setup") = require("./onboard/machine/handlers/agent-setup");
const { handleGatewayState }: typeof import("./onboard/machine/handlers/gateway") = require("./onboard/machine/handlers/gateway");
const { handlePoliciesState }: typeof import("./onboard/machine/handlers/policies") = require("./onboard/machine/handlers/policies");
const { handlePreflightState }: typeof import("./onboard/machine/handlers/preflight") = require("./onboard/machine/handlers/preflight");
const { handleProviderInferenceState }: typeof import("./onboard/machine/handlers/provider-inference") = require("./onboard/machine/handlers/provider-inference");
const { handleSandboxState }: typeof import("./onboard/machine/handlers/sandbox") = require("./onboard/machine/handlers/sandbox");
Expand Down Expand Up @@ -9553,97 +9554,40 @@
});
session = agentSetupResult.session;

const latestSession = onboardSession.loadSession();
const recordedPolicyPresets = Array.isArray(latestSession?.policyPresets)
? latestSession.policyPresets
: null;
const recordedMessagingChannels = Array.isArray(latestSession?.messagingChannels)
? latestSession.messagingChannels
: [];
const activeSandbox = registry.getSandbox(sandboxName);
const activeMessagingChannels = activeSandbox?.messagingChannels;
const policyMessagingChannels = mergePolicyMessagingChannels(
selectedMessagingChannels,
recordedMessagingChannels,
activeMessagingChannels,
activeSandbox?.disabledChannels,
);
verifyCompatibleEndpointSandboxSmoke({
const policiesResult = await handlePoliciesState({
resume,
sandboxName,
provider,
model,
runOpenshell,
redact,
endpointUrl,
credentialEnv,
messagingChannels: policyMessagingChannels,
selectedMessagingChannels,
webSearchConfig,
webSearchSupported,
hermesToolGateways,
agent,
});
const policyResumeSelection = preparePolicyPresetResumeSelection(
{ policies },
sandboxName,
{
recordedPolicyPresets,
disabledChannels: activeSandbox?.disabledChannels,
enabledChannels: policyMessagingChannels,
hermesToolGateways,
webSearchConfig,
webSearchSupported,
deps: {
loadSession: onboardSession.loadSession,
getActiveSandbox: (name) => registry.getSandbox(name),
mergePolicyMessagingChannels,
verifyCompatibleEndpointSandboxSmoke: (options) =>
verifyCompatibleEndpointSandboxSmoke({
...options,
runOpenshell,
redact,
}),
preparePolicyPresetResumeSelection: (name, options) =>
preparePolicyPresetResumeSelection({ policies }, name, options),
arePolicyPresetsApplied,
skippedStepMessage,
startRecordedStep,
setupPoliciesWithSelection,
updateSession: onboardSession.updateSession,
recordStepComplete,
toSessionUpdates: (updates) => toSessionUpdates(updates as Parameters<typeof toSessionUpdates>[0]),
},
);
const recordedPolicyPresetsForSupport = policyResumeSelection.policyPresets;
const resumePolicies =
resume &&
sandboxName &&
!policyResumeSelection.recordedPolicyPresetsNeedReconcile &&
!policyResumeSelection.disabledMessagingPolicyPresetApplied &&
arePolicyPresetsApplied(sandboxName, recordedPolicyPresetsForSupport);
if (resumePolicies) {
skippedStepMessage("policies", recordedPolicyPresetsForSupport.join(", "));
await recordStepComplete(
"policies",
toSessionUpdates({
sandboxName,
provider,
model,
policyPresets: recordedPolicyPresetsForSupport,
}),
);
} else {
await startRecordedStep("policies", {
sandboxName,
provider,
model,
policyPresets: recordedPolicyPresetsForSupport,
});
const setupAppliedPolicyPresets = await setupPoliciesWithSelection(sandboxName, {
selectedPresets:
Array.isArray(recordedPolicyPresets)
? recordedPolicyPresetsForSupport
: null,
enabledChannels: policyMessagingChannels,
disabledChannels: activeSandbox?.disabledChannels,
webSearchConfig,
provider,
webSearchSupported,
hermesToolGateways,
onSelection: (policyPresets) => {
onboardSession.updateSession((current: Session) => {
current.policyPresets = policyPresets;
return current;
});
},
});
await recordStepComplete(
"policies",
toSessionUpdates({
sandboxName,
provider,
model,
policyPresets: setupAppliedPolicyPresets,
}),
);
}
});
session = policiesResult.session;

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

The value assigned to session here is unused.

if (agent) {
ensureAgentDashboardForward(sandboxName, agent);
Expand Down
197 changes: 197 additions & 0 deletions src/lib/onboard/machine/handlers/policies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it, vi } from "vitest";

import { createSession, type Session, type SessionUpdates } from "../../../state/onboard-session";
import { handlePoliciesState, type PoliciesStateOptions } from "./policies";

type Agent = { name: string } | null;
type WebSearchConfig = { fetchEnabled: true };

function createDeps(overrides: Partial<PoliciesStateOptions<Agent, WebSearchConfig>["deps"]> = {}) {
let session = createSession();
const calls = {
load: vi.fn(() => session),
activeSandbox: vi.fn(() => ({ messagingChannels: ["telegram"], disabledChannels: null })),
mergeChannels: vi.fn(
(
selected: string[],
recorded: string[],
active: string[] | null | undefined,
) => (selected.length > 0 ? selected : active ?? recorded),
),
smoke: vi.fn(),
prepareResume: vi.fn(
(
_sandboxName: string,
options: Parameters<PoliciesStateOptions<Agent, WebSearchConfig>["deps"]["preparePolicyPresetResumeSelection"]>[1],
) => ({
policyPresets: (options.recordedPolicyPresets ?? []).filter((name) => name !== "unsupported"),
recordedPolicyPresetsNeedReconcile: (options.recordedPolicyPresets ?? []).includes("unsupported"),
disabledMessagingPolicyPresetApplied: false,
}),
),
appliedCheck: vi.fn(() => false),
skipped: vi.fn(),
startStep: vi.fn(async () => undefined),
setupPolicies: vi.fn(async () => ["npm"]),
updateSession: vi.fn((mutator: (value: Session) => Session | void) => {
session = mutator(session) ?? session;
return session;
}),
complete: vi.fn(async () => session),
};
return {
calls,
deps: {
loadSession: calls.load,
getActiveSandbox: calls.activeSandbox,
mergePolicyMessagingChannels: calls.mergeChannels,
verifyCompatibleEndpointSandboxSmoke: calls.smoke,
preparePolicyPresetResumeSelection: calls.prepareResume,
arePolicyPresetsApplied: calls.appliedCheck,
skippedStepMessage: calls.skipped,
startRecordedStep: calls.startStep,
setupPoliciesWithSelection: calls.setupPolicies,
updateSession: calls.updateSession,
recordStepComplete: calls.complete,
toSessionUpdates: (updates: Record<string, unknown>) => updates as SessionUpdates,
...overrides,
},
setSession(next: Session) {
session = next;
},
getSession: () => session,
};
}

function baseOptions(
deps: PoliciesStateOptions<Agent, WebSearchConfig>["deps"],
): PoliciesStateOptions<Agent, WebSearchConfig> {
return {
resume: false,
sandboxName: "my-assistant",
provider: "provider",
model: "model",
endpointUrl: "https://example.com/v1",
credentialEnv: "NVIDIA_API_KEY",
selectedMessagingChannels: [],
webSearchConfig: null,
webSearchSupported: true,
hermesToolGateways: [],
agent: null,
deps,
};
}

describe("handlePoliciesState", () => {
it("runs compatible endpoint smoke before policy selection", async () => {
const { deps, calls } = createDeps();

await handlePoliciesState(baseOptions(deps));

expect(calls.smoke).toHaveBeenCalledWith({
sandboxName: "my-assistant",
provider: "provider",
model: "model",
endpointUrl: "https://example.com/v1",
credentialEnv: "NVIDIA_API_KEY",
messagingChannels: ["telegram"],
agent: null,
});
expect(calls.startStep).toHaveBeenCalledWith("policies", {
sandboxName: "my-assistant",
provider: "provider",
model: "model",
policyPresets: [],
});
expect(calls.setupPolicies).toHaveBeenCalledWith(
"my-assistant",
expect.objectContaining({
selectedPresets: null,
enabledChannels: ["telegram"],
provider: "provider",
webSearchSupported: true,
}),
);
expect(calls.complete).toHaveBeenCalledWith(
"policies",
expect.objectContaining({ policyPresets: ["npm"] }),
);
});

it("uses recorded messaging channels when no active selection exists", async () => {
const session = createSession({ messagingChannels: ["slack"] });
const { deps, calls, setSession } = createDeps({
getActiveSandbox: vi.fn(() => ({ messagingChannels: null, disabledChannels: null })),
});
setSession(session);

await handlePoliciesState(baseOptions(deps));

expect(calls.setupPolicies).toHaveBeenCalledWith(
"my-assistant",
expect.objectContaining({ enabledChannels: ["slack"] }),
);
});

it("resumes policies when all recorded presets are already applied", async () => {
const session = createSession({ policyPresets: ["npm"] });
const { deps, calls, setSession } = createDeps({
arePolicyPresetsApplied: vi.fn(() => true),
});
setSession(session);

const result = await handlePoliciesState({ ...baseOptions(deps), resume: true });

expect(calls.skipped).toHaveBeenCalledWith("policies", "npm");
expect(calls.setupPolicies).not.toHaveBeenCalled();
expect(calls.complete).toHaveBeenCalledWith(
"policies",
expect.objectContaining({ policyPresets: ["npm"] }),
);
expect(result.appliedPolicyPresets).toEqual(["npm"]);
});

it("reconciles unsupported recorded presets before interactive setup", async () => {
const session = createSession({ policyPresets: ["npm", "unsupported"] });
const { deps, calls, setSession } = createDeps();
setSession(session);

await handlePoliciesState(baseOptions(deps));

expect(calls.prepareResume).toHaveBeenCalledWith(
"my-assistant",
expect.objectContaining({ recordedPolicyPresets: ["npm", "unsupported"] }),
);
expect(calls.setupPolicies).toHaveBeenCalledWith(
"my-assistant",
expect.objectContaining({ selectedPresets: ["npm"] }),
);
});

it("merges required Hermes tool gateway presets into recorded selections", async () => {
const session = createSession({ policyPresets: ["npm"] });
const prepareResume = vi.fn((_sandboxName, options) => ({
policyPresets: [...(options.recordedPolicyPresets ?? []), ...options.hermesToolGateways],
recordedPolicyPresetsNeedReconcile: false,
disabledMessagingPolicyPresetApplied: false,
}));
const { deps, calls, setSession } = createDeps({
preparePolicyPresetResumeSelection: prepareResume,
});
setSession(session);

await handlePoliciesState({ ...baseOptions(deps), hermesToolGateways: ["github"] });

expect(prepareResume).toHaveBeenCalledWith(
"my-assistant",
expect.objectContaining({ hermesToolGateways: ["github"] }),
);
expect(calls.setupPolicies).toHaveBeenCalledWith(
"my-assistant",
expect.objectContaining({ selectedPresets: ["npm", "github"] }),
);
});
});
Loading
Loading