Terminal/Context: harden control filtering and align argv root-value parsing

This commit is contained in:
Gustavo Madeira Santana
2026-03-02 19:44:39 -05:00
parent 40ed28f5c3
commit e3aee80358
4 changed files with 56 additions and 3 deletions

View File

@@ -33,4 +33,32 @@ describe("lookupContextTokens", () => {
const { lookupContextTokens } = await import("./context.js");
expect(lookupContextTokens("openrouter/claude-sonnet")).toBe(321_000);
});
it("does not skip eager warmup when --profile is followed by -- terminator", async () => {
const loadConfigMock = vi.fn(() => ({ models: {} }));
vi.doMock("../config/config.js", () => ({
loadConfig: loadConfigMock,
}));
vi.doMock("./models-config.js", () => ({
ensureOpenClawModelsJson: vi.fn(async () => {}),
}));
vi.doMock("./agent-paths.js", () => ({
resolveOpenClawAgentDir: () => "/tmp/openclaw-agent",
}));
vi.doMock("./pi-model-discovery.js", () => ({
discoverAuthStorage: vi.fn(() => ({})),
discoverModels: vi.fn(() => ({
getAll: () => [],
})),
}));
const argvSnapshot = process.argv;
process.argv = ["node", "openclaw", "--profile", "--", "config", "validate"];
try {
await import("./context.js");
expect(loadConfigMock).toHaveBeenCalledTimes(1);
} finally {
process.argv = argvSnapshot;
}
});
});

View File

@@ -69,10 +69,22 @@ const MODEL_CACHE = new Map<string, number>();
let loadPromise: Promise<void> | null = null;
let configuredWindowsPrimed = false;
function isValueToken(arg: string | undefined): boolean {
if (!arg || arg === "--") {
return false;
}
if (!arg.startsWith("-")) {
return true;
}
return /^-\d+(?:\.\d+)?$/.test(arg);
}
function getCommandPathFromArgv(argv: string[]): string[] {
const args = argv.slice(2);
const tokens: string[] = [];
let skipNextAsRootValue = false;
for (const arg of argv.slice(2)) {
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (!arg || arg === "--") {
break;
}
@@ -81,7 +93,8 @@ function getCommandPathFromArgv(argv: string[]): string[] {
continue;
}
if (arg === "--profile" || arg === "--log-level") {
skipNextAsRootValue = true;
const next = args[i + 1];
skipNextAsRootValue = isValueToken(next);
continue;
}
if (

View File

@@ -0,0 +1,12 @@
import { describe, expect, it } from "vitest";
import { sanitizeTerminalText } from "./safe-text.js";
describe("sanitizeTerminalText", () => {
it("removes C1 control characters", () => {
expect(sanitizeTerminalText("a\u009bb\u0085c")).toBe("abc");
});
it("escapes line controls while preserving printable text", () => {
expect(sanitizeTerminalText("a\tb\nc\rd")).toBe("a\\tb\\nc\\rd");
});
});

View File

@@ -11,7 +11,7 @@ export function sanitizeTerminalText(input: string): string {
let sanitized = "";
for (const char of normalized) {
const code = char.charCodeAt(0);
const isControl = (code >= 0x00 && code <= 0x1f) || code === 0x7f;
const isControl = (code >= 0x00 && code <= 0x1f) || (code >= 0x7f && code <= 0x9f);
if (!isControl) {
sanitized += char;
}