Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
73c58d2
refactor(cli): group remaining architecture modules
cv May 8, 2026
8f3ebc1
refactor(onboard): extract initial policy helpers
cv May 8, 2026
3d31299
refactor(onboard): extract compatible endpoint smoke helpers
cv May 8, 2026
05d1041
refactor(onboard): extract selection drift helpers
cv May 8, 2026
0055de0
refactor(onboard): extract config sync helpers
cv May 9, 2026
0b5c164
refactor(onboard): extract dockerfile patch helpers
cv May 9, 2026
c87f628
refactor(onboard): extract web search support helper
cv May 9, 2026
d113704
refactor(onboard): extract web search configuration flow
cv May 9, 2026
c74c829
Revert "refactor(onboard): extract web search configuration flow"
cv May 9, 2026
a0ebedc
refactor(onboard): extract web search configuration flow
cv May 9, 2026
b49d92d
refactor(onboard): extract web search verification probe
cv May 9, 2026
4c1ea77
refactor(onboard): extract gateway bootstrap repair helpers
cv May 9, 2026
57b3d61
Merge remote-tracking branch 'origin/main' into refactor/cli-architec…
cv May 9, 2026
1783c07
Merge branch 'refactor/cli-architecture-layout' into refactor/onboard…
cv May 9, 2026
66b0235
Merge branch 'refactor/onboard-initial-policy' into refactor/onboard-…
cv May 9, 2026
7ddddfb
Merge branch 'refactor/onboard-compatible-smoke' into refactor/onboar…
cv May 9, 2026
91d0e04
Merge branch 'refactor/onboard-selection-drift' into refactor/onboard…
cv May 9, 2026
af72a06
Merge branch 'refactor/onboard-config-sync' into refactor/onboard-doc…
cv May 9, 2026
a8d7346
Merge branch 'refactor/onboard-dockerfile-patch' into refactor/onboar…
cv May 9, 2026
4896d49
Merge branch 'refactor/onboard-web-search-support' into refactor/onbo…
cv May 9, 2026
86e664b
Merge branch 'refactor/onboard-web-search-config' into refactor/onboa…
cv May 9, 2026
7178b7e
Merge branch 'refactor/onboard-web-search-verify' into refactor/onboa…
cv May 9, 2026
bd0ffe5
test(inference): expect Kimi status thinking flag
cv May 9, 2026
5f51c03
Merge branch 'refactor/cli-architecture-layout' into refactor/onboard…
cv May 9, 2026
e3a0652
Merge branch 'refactor/onboard-initial-policy' into refactor/onboard-…
cv May 9, 2026
c525cbe
Merge branch 'refactor/onboard-compatible-smoke' into refactor/onboar…
cv May 9, 2026
9448574
Merge branch 'refactor/onboard-selection-drift' into refactor/onboard…
cv May 9, 2026
27c0246
Merge branch 'refactor/onboard-config-sync' into refactor/onboard-doc…
cv May 9, 2026
6d722f9
Merge branch 'refactor/onboard-dockerfile-patch' into refactor/onboar…
cv May 9, 2026
7e5c448
Merge branch 'refactor/onboard-web-search-support' into refactor/onbo…
cv May 9, 2026
73cd851
Merge branch 'refactor/onboard-web-search-config' into refactor/onboa…
cv May 9, 2026
97cfc13
Merge branch 'refactor/onboard-web-search-verify' into refactor/onboa…
cv May 9, 2026
374abc5
Merge remote-tracking branch 'origin/main' into refactor/cli-architec…
cv May 9, 2026
76500de
Merge branch 'refactor/cli-architecture-layout' into refactor/onboard…
cv May 9, 2026
78bd330
Merge branch 'refactor/onboard-initial-policy' into refactor/onboard-…
cv May 9, 2026
31c7252
Merge branch 'refactor/onboard-compatible-smoke' into refactor/onboar…
cv May 9, 2026
5545769
Merge branch 'refactor/onboard-selection-drift' into refactor/onboard…
cv May 9, 2026
0f458e2
Merge branch 'refactor/onboard-config-sync' into refactor/onboard-doc…
cv May 9, 2026
976bd01
Merge branch 'refactor/onboard-dockerfile-patch' into refactor/onboar…
cv May 9, 2026
6b50f4a
Merge branch 'refactor/onboard-web-search-support' into refactor/onbo…
cv May 9, 2026
2ca3a74
Merge branch 'refactor/onboard-web-search-config' into refactor/onboa…
cv May 9, 2026
b7cce3f
Merge branch 'refactor/onboard-web-search-verify' into refactor/onboa…
cv May 9, 2026
955225f
Merge branch 'main' into refactor/cli-architecture-layout
cv May 9, 2026
18ee079
merge main into cli architecture layout
cv May 9, 2026
2cbb302
test(policy): update tier onboarding policy import
cv May 9, 2026
ed06048
Merge branch 'refactor/cli-architecture-layout' of https://github.com…
cv May 9, 2026
d775298
Merge branch 'refactor/cli-architecture-layout' into refactor/onboard…
cv May 9, 2026
600994d
Merge branch 'refactor/onboard-initial-policy' into refactor/onboard-…
cv May 9, 2026
aed4efa
Merge branch 'refactor/onboard-compatible-smoke' into refactor/onboar…
cv May 9, 2026
a2950bd
Merge branch 'refactor/onboard-selection-drift' into refactor/onboard…
cv May 9, 2026
515d8d3
Merge branch 'refactor/onboard-config-sync' into refactor/onboard-doc…
cv May 9, 2026
27dff94
merge dockerfile patch into web search support
cv May 9, 2026
64324ef
merge(onboard): bring web search support up to date
cv May 9, 2026
3a225ba
merge(onboard): bring onboard-web-search-config into onboard-web-sear…
cv May 9, 2026
fabec79
merge(onboard): bring onboard-web-search-verify into onboard-gateway-…
cv May 9, 2026
13bcc3a
Merge branch 'main' into refactor/cli-architecture-layout
cv May 10, 2026
96008e0
merge(main): update architecture layout stack base
cv May 11, 2026
32cb2b7
merge(stack): update initial policy helpers branch
cv May 11, 2026
43f8482
merge(stack): update compatible smoke branch
cv May 11, 2026
88eb34d
merge(stack): update selection drift branch
cv May 11, 2026
11d9014
merge(stack): update config sync branch
cv May 11, 2026
548a45a
merge(stack): update dockerfile patch branch
cv May 11, 2026
1fea709
merge(stack): update web search support branch
cv May 11, 2026
9523c5a
merge(stack): update web search config branch
cv May 11, 2026
c54910f
merge(stack): update web search verification branch
cv May 11, 2026
25ddfb3
merge(stack): update gateway bootstrap branch
cv May 11, 2026
c86a8ed
merge(main): refresh architecture layout branch
cv May 11, 2026
31b255d
Merge branch 'main' into refactor/cli-architecture-layout
cv May 11, 2026
23efdf3
Merge branch 'main' into refactor/cli-architecture-layout
cv May 11, 2026
37d5704
merge(main): refresh architecture layout branch
cv May 11, 2026
5177213
Potential fix for pull request finding 'CodeQL / Unused variable, imp…
cv May 11, 2026
de52330
Merge branch 'refactor/cli-architecture-layout' into refactor/onboard…
cv May 11, 2026
3faec14
Merge branch 'refactor/onboard-initial-policy' into refactor/onboard-…
cv May 11, 2026
cf9c4ce
Merge branch 'refactor/onboard-compatible-smoke' into refactor/onboar…
cv May 11, 2026
a77f84b
Merge branch 'refactor/onboard-selection-drift' into refactor/onboard…
cv May 11, 2026
e716b66
Merge branch 'refactor/onboard-config-sync' into refactor/onboard-doc…
cv May 11, 2026
c1a1535
Merge branch 'refactor/onboard-dockerfile-patch' into refactor/onboar…
cv May 11, 2026
5a4da27
Merge branch 'refactor/onboard-web-search-support' into refactor/onbo…
cv May 11, 2026
14494ce
Merge branch 'refactor/onboard-web-search-config' into refactor/onboa…
cv May 11, 2026
a8a79ff
Merge branch 'refactor/onboard-web-search-verify' into refactor/onboa…
cv May 11, 2026
20cf1ab
Apply suggestions from code review
cv May 11, 2026
5aa5955
merge: origin/main into refactor/onboard-web-search-verify
cv May 13, 2026
9099691
refactor(onboard): extract web search config and verification helpers
cv May 13, 2026
1404c87
Merge branch 'main' into refactor/onboard-web-search-verify
cjagwani May 13, 2026
ac31da8
Potential fix for pull request finding 'CodeQL / Unused variable, imp…
cv May 13, 2026
5aabaff
refactor(onboard): extract web search verification probe
cv May 13, 2026
673454c
merge: origin/main into refactor/onboard-web-search-verify
cv May 13, 2026
2a30b20
refactor(onboard): refresh web search verification against main
cv May 13, 2026
611ca91
Merge branch 'main' into refactor/onboard-web-search-verify
cv May 13, 2026
9273b30
Merge branch 'main' into refactor/onboard-web-search-verify
cjagwani May 13, 2026
72be50d
Merge branch 'main' into refactor/onboard-web-search-verify
cjagwani May 13, 2026
5f6db8a
refactor(onboard): extract dashboard access helpers (#3307)
cv May 13, 2026
6fd2914
Merge remote-tracking branch 'origin/refactor/onboard-web-search-veri…
ericksoa May 13, 2026
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
373 changes: 56 additions & 317 deletions src/lib/onboard.ts

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions src/lib/onboard/dashboard-access.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 {
buildAuthenticatedDashboardUrl,
dashboardUrlForDisplay,
getDashboardAccessInfo,
getDashboardForwardPort,
getDashboardForwardStartCommand,
getDashboardForwardTarget,
getDashboardGuidanceLines,
getWslHostAddress,
} from "./dashboard-access";

describe("dashboard access helpers", () => {
it("derives forward port and target from chat UI URLs", () => {
expect(getDashboardForwardPort("http://127.0.0.1:18789", { isWsl: false })).toBe("18789");
expect(getDashboardForwardTarget("http://127.0.0.1:18789", { isWsl: false })).toBe("18789");
expect(getDashboardForwardTarget("http://10.0.0.25:18789", { isWsl: false })).toBe("0.0.0.0:18789");
});

it("builds the OpenShell forward start command with the resolved target", () => {
const openshellShellCommand = vi.fn((args: string[]) => `openshell ${args.join(" ")}`);

expect(
getDashboardForwardStartCommand("alpha", {
chatUiUrl: "http://10.0.0.25:18789",
openshellShellCommand,
}),
).toBe("openshell forward start --background 0.0.0.0:18789 alpha");
});

it("redacts token fragments for display", () => {
expect(buildAuthenticatedDashboardUrl("http://127.0.0.1:18789/", "secret token")).toBe(
"http://127.0.0.1:18789/#token=secret%20token",
);
expect(dashboardUrlForDisplay("http://127.0.0.1:18789/#token=secret", (value) => value)).toBe(
"http://127.0.0.1:18789/",
);
});

it("detects a WSL host address only when WSL is active", () => {
const runCapture = vi.fn(() => "172.22.1.1 10.0.0.2\n");

expect(getWslHostAddress({ isWsl: true, runCapture })).toBe("172.22.1.1");
expect(getWslHostAddress({ isWsl: false, runCapture })).toBeNull();
});

it("builds dashboard access entries including a WSL URL", () => {
const access = getDashboardAccessInfo("alpha", {
token: "secret",
chatUiUrl: "http://127.0.0.1:18789",
isWsl: true,
wslHostAddress: "172.22.1.1",
});

expect(access).toContainEqual({
label: "Alt 1",
url: "http://172.22.1.1:18789/#token=secret",
});
});

it("builds dashboard guidance for WSL and empty access lists", () => {
expect(
getDashboardGuidanceLines([], {
chatUiUrl: "http://127.0.0.1:18789",
isWsl: true,
}),
).toEqual([
"Port 18789 must be forwarded before opening these URLs.",
"WSL detected: if localhost fails in Windows, use the WSL host IP shown by `hostname -I`.",
"No dashboard URLs were generated.",
]);
});
});
144 changes: 144 additions & 0 deletions src/lib/onboard/dashboard-access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { WslDetectionOptions } from "../platform";
import { isWsl } from "../platform";
import { DASHBOARD_PORT } from "../core/ports";
import { buildChain, buildControlUiUrls } from "../dashboard/contract";

type RunCapture = (args: string[], options: { ignoreError: true }) => string;
type OpenshellShellCommand = (args: string[], options?: { openshellBinary?: string }) => string;

export type DashboardAccessOptions = WslDetectionOptions & {
chatUiUrl?: string;
token?: string | null;
wslHostAddress?: string | null;
runCapture?: RunCapture;
openshellBinary?: string;
openshellShellCommand?: OpenshellShellCommand;
fetchGatewayAuthToken?: (sandboxName: string) => string | null;
env?: NodeJS.ProcessEnv;
};

export type DashboardAccessEntry = {
label: string;
url: string;
};

const CONTROL_UI_PORT = DASHBOARD_PORT;

function defaultChatUiUrl(options: DashboardAccessOptions = {}): string {
return options.chatUiUrl || options.env?.CHAT_UI_URL || process.env.CHAT_UI_URL || `http://127.0.0.1:${CONTROL_UI_PORT}`;
}

export function getWslHostAddress(options: DashboardAccessOptions = {}): string | null {
if (options.wslHostAddress) {
return options.wslHostAddress;
}
if (!isWsl(options)) {
return null;
}
const runCaptureFn = options.runCapture;
if (!runCaptureFn) return null;
const output = runCaptureFn(["hostname", "-I"], { ignoreError: true });
return (
String(output || "")
.trim()
.split(/\s+/)
.filter(Boolean)[0] || null
);
}

export function buildDashboardChain(
chatUiUrl = defaultChatUiUrl(),
options: DashboardAccessOptions = {},
) {
return buildChain({
chatUiUrl,
isWsl: isWsl(options),
wslHostAddress: getWslHostAddress(options),
});
}

export function getDashboardForwardPort(
chatUiUrl = defaultChatUiUrl(),
options: DashboardAccessOptions = {},
): string {
return String(buildDashboardChain(chatUiUrl, options).port);
}

export function getDashboardForwardTarget(
chatUiUrl = defaultChatUiUrl(),
options: DashboardAccessOptions = {},
): string {
return buildDashboardChain(chatUiUrl, options).forwardTarget;
}

export function getDashboardForwardStartCommand(
sandboxName: string,
options: DashboardAccessOptions = {},
): string {
if (!options.openshellShellCommand) {
throw new Error("getDashboardForwardStartCommand requires openshellShellCommand");
}
const chatUiUrl = defaultChatUiUrl(options);
const forwardTarget = getDashboardForwardTarget(chatUiUrl, options);
return `${options.openshellShellCommand(
["forward", "start", "--background", forwardTarget, sandboxName],
options,
)}`;
}

export function buildAuthenticatedDashboardUrl(baseUrl: string, token: string | null = null): string {
if (!token) return baseUrl;
return `${baseUrl}#token=${encodeURIComponent(token)}`;
}

export function dashboardUrlForDisplay(url: string, redact: (value: string) => string = (value) => value): string {
return redact(url.replace(/#token=[^\s'"]*$/i, ""));
}

export function getDashboardAccessInfo(
sandboxName: string,
options: DashboardAccessOptions = {},
): DashboardAccessEntry[] {
const token = Object.prototype.hasOwnProperty.call(options, "token")
? options.token
: options.fetchGatewayAuthToken?.(sandboxName) ?? null;
const chatUiUrl = defaultChatUiUrl(options);
const chain = buildDashboardChain(chatUiUrl, options);
const dashboardAccess = buildControlUiUrls(token ?? null, chain.port, chain.accessUrl).map(
(url, index) => ({
label: index === 0 ? "Dashboard" : `Alt ${index}`,
url: buildAuthenticatedDashboardUrl(url, null),
}),
);

const wslHostAddress = getWslHostAddress(options);
if (wslHostAddress) {
const wslUrl = buildAuthenticatedDashboardUrl(`http://${wslHostAddress}:${chain.port}/`, token ?? null);
if (!dashboardAccess.some((access) => access.url === wslUrl)) {
dashboardAccess.push({ label: "VS Code/WSL", url: wslUrl });
}
}

return dashboardAccess;
}

export function getDashboardGuidanceLines(
dashboardAccess: DashboardAccessEntry[] = [],
options: DashboardAccessOptions = {},
): string[] {
const chatUiUrl = defaultChatUiUrl(options);
const chain = buildDashboardChain(chatUiUrl, options);
const guidance = [`Port ${String(chain.port)} must be forwarded before opening these URLs.`];
if (isWsl(options)) {
guidance.push(
"WSL detected: if localhost fails in Windows, use the WSL host IP shown by `hostname -I`.",
);
}
if (dashboardAccess.length === 0) {
guidance.push("No dashboard URLs were generated.");
}
return guidance;
}
86 changes: 86 additions & 0 deletions src/lib/onboard/gateway-bootstrap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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 {
buildGatewayBootstrapSecretsScript,
createGatewayBootstrapRepairHelpers,
getGatewayBootstrapRepairPlan,
} from "./gateway-bootstrap";

describe("gateway bootstrap helpers", () => {
it("normalizes missing secrets into a repair plan", () => {
expect(
getGatewayBootstrapRepairPlan([
"openshell-client-tls",
"noise",
" openshell-server-tls ",
"",
]),
).toEqual({
missingSecrets: ["openshell-client-tls", "openshell-server-tls"],
needsRepair: true,
needsServerTls: true,
needsClientBundle: true,
needsHandshake: false,
});
});

it("builds a no-op script when nothing is missing", () => {
expect(buildGatewayBootstrapSecretsScript([]).trim()).toBe("exit 0");
});

it("builds a repair script only for requested secret classes", () => {
const script = buildGatewayBootstrapSecretsScript([
"openshell-server-tls",
"openshell-ssh-handshake",
]);

expect(script).toContain("openshell-server-tls");
expect(script).toContain("openshell-ssh-handshake");
expect(script).toContain("if true; then");
expect(script).toContain("if false; then");
});

it("lists missing secrets through the gateway cluster executor", () => {
const buildGatewayClusterExecArgv = vi.fn((script: string) => ["docker", "exec", script]);
const runCapture = vi.fn(() => "openshell-client-tls\n\nopenshell-server-tls\n");
const helpers = createGatewayBootstrapRepairHelpers({
buildGatewayClusterExecArgv,
run: vi.fn(() => ({ status: 0 })),
runCapture,
});

expect(helpers.listMissingGatewayBootstrapSecrets()).toEqual([
"openshell-client-tls",
"openshell-server-tls",
]);
expect(runCapture).toHaveBeenCalledWith(
expect.arrayContaining([expect.stringContaining("kubectl -n openshell get secret")]),
{ ignoreError: true },
);
});

it("repairs missing secrets and reports success when the second probe is clean", () => {
const log = vi.fn();
const runCapture = vi
.fn()
.mockReturnValueOnce("openshell-client-tls\n")
.mockReturnValueOnce("");
const run = vi.fn(() => ({ status: 0 }));
const helpers = createGatewayBootstrapRepairHelpers({
buildGatewayClusterExecArgv: (script) => ["docker", "exec", script],
run,
runCapture,
log,
});

expect(helpers.repairGatewayBootstrapSecrets()).toEqual({ repaired: true, missingSecrets: [] });
expect(run).toHaveBeenCalledWith(expect.any(Array), {
ignoreError: true,
suppressOutput: true,
});
expect(log).toHaveBeenCalledWith(" ✓ OpenShell bootstrap secrets created");
});
});
Loading
Loading