Merge pull request #442 from manaflow-ai/issue-441-browser-eval-result-output
Fix browser eval CLI output to return value
This commit is contained in:
commit
8df13d10b0
3 changed files with 232 additions and 3 deletions
|
|
@ -2007,6 +2007,54 @@ struct CMUXCLI {
|
|||
}
|
||||
}
|
||||
|
||||
func displayBrowserValue(_ value: Any) -> String {
|
||||
if value is NSNull {
|
||||
return "null"
|
||||
}
|
||||
if let string = value as? String {
|
||||
return string
|
||||
}
|
||||
if let bool = value as? Bool {
|
||||
return bool ? "true" : "false"
|
||||
}
|
||||
if let number = value as? NSNumber {
|
||||
return number.stringValue
|
||||
}
|
||||
if JSONSerialization.isValidJSONObject(value),
|
||||
let data = try? JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted]),
|
||||
let text = String(data: data, encoding: .utf8) {
|
||||
return text
|
||||
}
|
||||
return String(describing: value)
|
||||
}
|
||||
|
||||
func displayBrowserLogItems(_ value: Any?) -> String? {
|
||||
guard let items = value as? [Any], !items.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let lines = items.map { item -> String in
|
||||
guard let dict = item as? [String: Any] else {
|
||||
return displayBrowserValue(item)
|
||||
}
|
||||
|
||||
let text = (dict["text"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let levelRaw = (dict["level"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let level = levelRaw.isEmpty ? "log" : levelRaw
|
||||
|
||||
if text.isEmpty {
|
||||
if let message = (dict["message"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!message.isEmpty {
|
||||
return "[error] \(message)"
|
||||
}
|
||||
return displayBrowserValue(dict)
|
||||
}
|
||||
return "[\(level)] \(text)"
|
||||
}
|
||||
|
||||
return lines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
func nonFlagArgs(_ values: [String]) -> [String] {
|
||||
values.filter { !$0.hasPrefix("-") }
|
||||
}
|
||||
|
|
@ -2174,7 +2222,13 @@ struct CMUXCLI {
|
|||
throw CLIError(message: "browser eval requires a script")
|
||||
}
|
||||
let payload = try client.sendV2(method: "browser.eval", params: ["surface_id": sid, "script": trimmed])
|
||||
output(payload, fallback: "OK")
|
||||
let fallback: String
|
||||
if let value = payload["value"] {
|
||||
fallback = displayBrowserValue(value)
|
||||
} else {
|
||||
fallback = "OK"
|
||||
}
|
||||
output(payload, fallback: fallback)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -2785,7 +2839,8 @@ struct CMUXCLI {
|
|||
throw CLIError(message: "Unsupported browser console subcommand: \(consoleVerb)")
|
||||
}
|
||||
let payload = try client.sendV2(method: method, params: ["surface_id": sid])
|
||||
output(payload, fallback: "OK")
|
||||
let fallback = displayBrowserLogItems(payload["entries"]) ?? "OK"
|
||||
output(payload, fallback: fallback)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -2799,7 +2854,8 @@ struct CMUXCLI {
|
|||
throw CLIError(message: "Unsupported browser errors subcommand: \(errorsVerb)")
|
||||
}
|
||||
let payload = try client.sendV2(method: "browser.errors.list", params: params)
|
||||
output(payload, fallback: "OK")
|
||||
let fallback = displayBrowserLogItems(payload["errors"]) ?? "OK"
|
||||
output(payload, fallback: fallback)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
86
tests/test_browser_console_errors_cli_output_regression.py
Normal file
86
tests/test_browser_console_errors_cli_output_regression.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Static regression guard for browser console/errors CLI output formatting.
|
||||
|
||||
Ensures non-JSON `browser console list` and `browser errors list` do not fall
|
||||
back to unconditional `OK` when logs exist.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return Path(result.stdout.strip())
|
||||
return Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def extract_block(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
raise ValueError(f"Missing signature: {signature}")
|
||||
brace_start = source.find("{", start)
|
||||
if brace_start < 0:
|
||||
raise ValueError(f"Missing opening brace for: {signature}")
|
||||
depth = 0
|
||||
for idx in range(brace_start, len(source)):
|
||||
char = source[idx]
|
||||
if char == "{":
|
||||
depth += 1
|
||||
elif char == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace_start : idx + 1]
|
||||
raise ValueError(f"Unbalanced braces for: {signature}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
failures: list[str] = []
|
||||
|
||||
cli_path = root / "CLI" / "cmux.swift"
|
||||
cli_source = cli_path.read_text(encoding="utf-8")
|
||||
browser_block = extract_block(cli_source, "private func runBrowserCommand(")
|
||||
|
||||
if "func displayBrowserLogItems(_ value: Any?) -> String?" not in browser_block:
|
||||
failures.append("runBrowserCommand() is missing displayBrowserLogItems() helper")
|
||||
else:
|
||||
helper_block = extract_block(browser_block, "func displayBrowserLogItems(_ value: Any?) -> String?")
|
||||
if "return \"[\\(level)] \\(text)\"" not in helper_block:
|
||||
failures.append("displayBrowserLogItems() no longer renders level-prefixed log lines")
|
||||
if "return \"[error] \\(message)\"" not in helper_block:
|
||||
failures.append("displayBrowserLogItems() no longer renders concise JS error messages")
|
||||
if "return displayBrowserValue(dict)" not in helper_block:
|
||||
failures.append("displayBrowserLogItems() no longer falls back to structured formatting")
|
||||
|
||||
console_block = extract_block(browser_block, 'if subcommand == "console"')
|
||||
if 'displayBrowserLogItems(payload["entries"])' not in console_block:
|
||||
failures.append("browser console path no longer formats entries for non-JSON output")
|
||||
if 'output(payload, fallback: "OK")' in console_block:
|
||||
failures.append("browser console path regressed to unconditional OK output")
|
||||
|
||||
errors_block = extract_block(browser_block, 'if subcommand == "errors"')
|
||||
if 'displayBrowserLogItems(payload["errors"])' not in errors_block:
|
||||
failures.append("browser errors path no longer formats errors for non-JSON output")
|
||||
if 'output(payload, fallback: "OK")' in errors_block:
|
||||
failures.append("browser errors path regressed to unconditional OK output")
|
||||
|
||||
if failures:
|
||||
print("FAIL: browser console/errors CLI output regression guard failed")
|
||||
for item in failures:
|
||||
print(f" - {item}")
|
||||
return 1
|
||||
|
||||
print("PASS: browser console/errors CLI output regression guard is in place")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
87
tests/test_browser_eval_cli_output_regression.py
Normal file
87
tests/test_browser_eval_cli_output_regression.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Static regression guard for browser eval CLI output formatting.
|
||||
|
||||
Ensures `cmux browser <surface> eval <script>` prints the evaluated value
|
||||
instead of always printing `OK`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return Path(result.stdout.strip())
|
||||
return Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def extract_block(source: str, signature: str) -> str:
|
||||
start = source.find(signature)
|
||||
if start < 0:
|
||||
raise ValueError(f"Missing signature: {signature}")
|
||||
brace_start = source.find("{", start)
|
||||
if brace_start < 0:
|
||||
raise ValueError(f"Missing opening brace for: {signature}")
|
||||
depth = 0
|
||||
for idx in range(brace_start, len(source)):
|
||||
char = source[idx]
|
||||
if char == "{":
|
||||
depth += 1
|
||||
elif char == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return source[brace_start : idx + 1]
|
||||
raise ValueError(f"Unbalanced braces for: {signature}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
failures: list[str] = []
|
||||
|
||||
cli_path = root / "CLI" / "cmux.swift"
|
||||
cli_source = cli_path.read_text(encoding="utf-8")
|
||||
browser_block = extract_block(cli_source, "private func runBrowserCommand(")
|
||||
|
||||
if "func displayBrowserValue(_ value: Any) -> String" not in browser_block:
|
||||
failures.append("runBrowserCommand() is missing displayBrowserValue() helper")
|
||||
else:
|
||||
value_block = extract_block(browser_block, "func displayBrowserValue(_ value: Any) -> String")
|
||||
required_guards = [
|
||||
"if value is NSNull",
|
||||
"if let string = value as? String",
|
||||
"if let bool = value as? Bool",
|
||||
"if let number = value as? NSNumber",
|
||||
]
|
||||
for guard in required_guards:
|
||||
if guard not in value_block:
|
||||
failures.append(f"displayBrowserValue() no longer handles: {guard}")
|
||||
|
||||
eval_block = extract_block(browser_block, 'if subcommand == "eval"')
|
||||
if 'let payload = try client.sendV2(method: "browser.eval"' not in eval_block:
|
||||
failures.append("browser eval path no longer calls browser.eval v2 method")
|
||||
if 'if let value = payload["value"]' not in eval_block:
|
||||
failures.append("browser eval path no longer reads payload value")
|
||||
if "fallback = displayBrowserValue(value)" not in eval_block:
|
||||
failures.append("browser eval path no longer formats payload value for CLI output")
|
||||
if 'output(payload, fallback: "OK")' in eval_block:
|
||||
failures.append("browser eval path regressed to unconditional OK output")
|
||||
|
||||
if failures:
|
||||
print("FAIL: browser eval CLI output regression guard failed")
|
||||
for item in failures:
|
||||
print(f" - {item}")
|
||||
return 1
|
||||
|
||||
print("PASS: browser eval CLI output regression guard is in place")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue