-
Notifications
You must be signed in to change notification settings - Fork 2.8k
test(e2e): migrate messaging compatible endpoint #5362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
30d7e03
test(e2e): migrate messaging compatible endpoint
cv fe649c6
test(e2e): address messaging endpoint review
cv 825a2af
test(e2e): harden messaging compatible endpoint checks
cv 9f0d468
test(e2e): tighten messaging endpoint assertions
cv c316b75
test(e2e): prove messaging endpoint rate-limit source
cv 03fdfc0
test(e2e): narrow messaging endpoint rate-limit skip
cv 2956eb5
test(e2e): fail closed for messaging endpoint migration
cv 20475f1
test(e2e): guard transient provider classifier
cv c934235
test(e2e): avoid newline args in network policy probes
cv 86b87a7
test(e2e): accept blocked slack fetch errors
cv bc3b9da
test(e2e): preserve network policy shell quoting
cv c97fd9d
test(e2e): cover messaging endpoint helpers
cv c1d0f58
test(e2e): fix network policy preset selector
cv 1a9e586
test(e2e): refine live scenario helpers
cv e09fde8
chore: merge main into messaging compatible endpoint PR
cv 6bf2bad
Merge remote-tracking branch 'origin/main' into codex/5098-messaging-…
cv 073f89b
Merge branch 'main' into codex/5098-messaging-compatible-endpoint
cv 34a648e
Merge remote-tracking branch 'origin/main' into codex/5098-messaging-…
cv c6ed241
Merge remote-tracking branch 'origin/main' into codex/5098-messaging-…
cv eca4368
Merge remote-tracking branch 'origin/main' into codex/5098-messaging-…
cv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
199 changes: 199 additions & 0 deletions
199
test/e2e-scenario/live/messaging-compatible-endpoint-helpers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import path from "node:path"; | ||
|
|
||
| import { buildAvailabilityProbeEnv } from "../fixtures/availability-env.ts"; | ||
| import type { HostCliClient } from "../fixtures/clients/host.ts"; | ||
|
|
||
| const REPO_ROOT = path.resolve(import.meta.dirname, "../../.."); | ||
| const CLI_ENTRYPOINT = path.join(REPO_ROOT, "bin", "nemoclaw.js"); | ||
|
|
||
| export function commandEnv(extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv { | ||
| return { | ||
| ...buildAvailabilityProbeEnv(), | ||
| ...extra, | ||
| NEMOCLAW_NON_INTERACTIVE: "1", | ||
| NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1", | ||
| OPENSHELL_GATEWAY: process.env.OPENSHELL_GATEWAY ?? "nemoclaw", | ||
| }; | ||
| } | ||
|
|
||
| async function bestEffort(run: () => Promise<unknown>): Promise<void> { | ||
| try { | ||
| await run(); | ||
| } catch { | ||
| // Best-effort cleanup mirrors the legacy shell teardown. | ||
| // Narrow this once NemoClaw/OpenShell/gateway teardown treats missing | ||
| // resources as successful cleanup. | ||
| } | ||
| } | ||
|
|
||
| export async function stopGatewayRuntime(host: HostCliClient, artifactName: string): Promise<void> { | ||
| await bestEffort(() => | ||
| host.command( | ||
| "bash", | ||
| [ | ||
| "-lc", | ||
| [ | ||
| "set +e", | ||
| "openshell forward stop 18789 >/dev/null 2>&1", | ||
| "openshell gateway stop -g nemoclaw >/dev/null 2>&1", | ||
| 'pid_file="$HOME/.local/state/nemoclaw/openshell-docker-gateway/openshell-gateway.pid"', | ||
| 'if [ -f "$pid_file" ]; then', | ||
| ' pid="$(tr -d "[:space:]" <"$pid_file" 2>/dev/null || true)"', | ||
| ' if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then', | ||
| ' kill "$pid" 2>/dev/null || true', | ||
| " for _ in $(seq 1 10); do", | ||
| ' kill -0 "$pid" 2>/dev/null || break', | ||
| " sleep 1", | ||
| " done", | ||
| ' kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true', | ||
| " fi", | ||
| "fi", | ||
| 'cid="$(docker ps -qf "name=openshell-cluster-nemoclaw" 2>/dev/null | head -1)"', | ||
| 'if [ -n "$cid" ]; then docker stop "$cid" >/dev/null 2>&1 || true; fi', | ||
| "openshell gateway remove nemoclaw >/dev/null 2>&1", | ||
| "openshell gateway destroy -g nemoclaw >/dev/null 2>&1", | ||
| "exit 0", | ||
| ].join("\n"), | ||
| ], | ||
| { | ||
| artifactName, | ||
| env: commandEnv(), | ||
| timeoutMs: 90_000, | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
|
|
||
| export async function cleanupMessagingState( | ||
| host: HostCliClient, | ||
| sandboxName: string, | ||
| ): Promise<void> { | ||
| // Endpoint-validation skips can happen before the sandbox exists. Keep | ||
| // teardown non-throwing so "Sandbox ... does not exist" stays a normal | ||
| // pre-contract cleanup outcome instead of masking the original evidence. | ||
| await bestEffort(() => | ||
| host.command("node", [CLI_ENTRYPOINT, sandboxName, "destroy", "--yes"], { | ||
| artifactName: `cleanup-nemoclaw-destroy-${sandboxName}`, | ||
| env: commandEnv(), | ||
| timeoutMs: 120_000, | ||
| }), | ||
| ); | ||
| await bestEffort(() => | ||
| host.command("openshell", ["sandbox", "delete", sandboxName], { | ||
| artifactName: `cleanup-openshell-sandbox-delete-${sandboxName}`, | ||
| env: commandEnv(), | ||
| timeoutMs: 60_000, | ||
| }), | ||
| ); | ||
| await stopGatewayRuntime(host, "cleanup-openshell-gateway-runtime-nemoclaw"); | ||
| } | ||
|
|
||
| function findJsonObjectEnd(raw: string, start: number): number | null { | ||
| let depth = 0; | ||
| let inString = false; | ||
| let escaped = false; | ||
| for (let index = start; index < raw.length; index += 1) { | ||
| const char = raw[index]; | ||
| if (inString) { | ||
| if (escaped) { | ||
| escaped = false; | ||
| } else if (char === "\\") { | ||
| escaped = true; | ||
| } else if (char === '"') { | ||
| inString = false; | ||
| } | ||
| continue; | ||
| } | ||
| if (char === '"') { | ||
| inString = true; | ||
| } else if (char === "{") { | ||
| depth += 1; | ||
| } else if (char === "}") { | ||
| depth -= 1; | ||
| if (depth === 0) return index + 1; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| export function parseOpenClawAgentText(raw: string): string { | ||
| if (!raw.trim()) return ""; | ||
| const parts: string[] = []; | ||
| const visited = new Set<unknown>(); | ||
| const textKeys = new Set(["text", "content", "reasoning_content"]); | ||
| const containerKeys = new Set([ | ||
| "result", | ||
| "payloads", | ||
| "payload", | ||
| "messages", | ||
| "choices", | ||
| "response", | ||
| "data", | ||
| "output", | ||
| "outputs", | ||
| "items", | ||
| "segments", | ||
| "delta", | ||
| ]); | ||
|
|
||
| const add = (value: unknown) => { | ||
| if (typeof value === "string" && value.trim()) parts.push(value.trim()); | ||
| }; | ||
| const collect = (value: unknown) => { | ||
| if (visited.has(value)) return; | ||
| visited.add(value); | ||
| if (typeof value === "string") { | ||
| add(value); | ||
| return; | ||
| } | ||
| if (Array.isArray(value)) { | ||
| value.forEach(collect); | ||
| return; | ||
| } | ||
| if (!value || typeof value !== "object") return; | ||
| const record = value as Record<string, unknown>; | ||
| for (const key of textKeys) { | ||
| if (key in record) collect(record[key]); | ||
| } | ||
| const choices = record.choices; | ||
| if (Array.isArray(choices)) { | ||
| for (const choice of choices) { | ||
| if (!choice || typeof choice !== "object") continue; | ||
| collect((choice as Record<string, unknown>).message); | ||
| collect((choice as Record<string, unknown>).delta); | ||
| add((choice as Record<string, unknown>).text); | ||
| } | ||
| } | ||
| for (const key of containerKeys) { | ||
| if (key in record) collect(record[key]); | ||
| } | ||
| }; | ||
| const collectDoc = (doc: unknown) => { | ||
| if (doc && typeof doc === "object" && (doc as Record<string, unknown>).result) { | ||
| collect((doc as Record<string, unknown>).result); | ||
| } else { | ||
| collect(doc); | ||
| } | ||
| }; | ||
|
|
||
| try { | ||
| collectDoc(JSON.parse(raw)); | ||
| } catch { | ||
| for (const match of raw.matchAll(/{/g)) { | ||
| try { | ||
| const before = parts.length; | ||
| const start = match.index; | ||
| const end = findJsonObjectEnd(raw, start); | ||
| if (end === null) continue; | ||
| collectDoc(JSON.parse(raw.slice(start, end))); | ||
| if (parts.length > before) break; | ||
| } catch { | ||
| // Continue scanning for a later JSON object, matching the legacy parser. | ||
| } | ||
| } | ||
| } | ||
| return parts.join("\n"); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.