diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1f320..9fedd9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,9 @@ Cutting that release is tracked in ### Changed +- Removed stale screenshot and JSON conversion paths, honored per-operation + Linux evaluation timeouts, and stopped advertising the compatibility-only + `--json` flag. - QA evidence now includes a recorded progressive-pruning scenario, and the committed bundle is checksum-verified in CI. - Repository moved to the `LockInTime` organisation; site links updated. diff --git a/apps/headless/Host/AgentBridge.swift b/apps/headless/Host/AgentBridge.swift index 4b71198..98e3431 100644 --- a/apps/headless/Host/AgentBridge.swift +++ b/apps/headless/Host/AgentBridge.swift @@ -404,7 +404,7 @@ extension BrowserWindowController { throw HostError(code: .operationFailed, message: "Browser returned an invalid agent result") } do { - return try unwrapAgentEvaluationResult(jsonValue(from: result.get())) + return try unwrapAgentEvaluationResult(JSONValue.foundationValue(result.get())) } catch let error as HostError { throw error } catch { @@ -417,17 +417,3 @@ private func onMain(_ body: @escaping () -> T) -> T { if Thread.isMainThread { return body() } return DispatchQueue.main.sync(execute: body) } - -private func jsonValue(from value: Any) throws -> JSONValue { - switch value { - case let value as String: return .string(value) - case let value as NSNumber: - if CFGetTypeID(value) == CFBooleanGetTypeID() { return .bool(value.boolValue) } - return .number(value.doubleValue) - case let value as [Any]: return .array(try value.map(jsonValue(from:))) - case let value as [String: Any]: - return .object(try value.mapValues(jsonValue(from:))) - case is NSNull: return .null - default: throw HostError(code: .operationFailed, message: "Browser returned an invalid agent result") - } -} diff --git a/apps/headless/LinuxHost/BrowserProcess.swift b/apps/headless/LinuxHost/BrowserProcess.swift index ac0d01c..624e372 100644 --- a/apps/headless/LinuxHost/BrowserProcess.swift +++ b/apps/headless/LinuxHost/BrowserProcess.swift @@ -480,14 +480,10 @@ final class LinuxBrowserSession: @unchecked Sendable { if hasTarget { throw CDPError.commandFailed("PDF capture is not supported for element targets") } - var printParameters: [String: Any] = [ + let printParameters: [String: Any] = [ "printBackground": true, "preferCSSPageSize": true, ] - if parameters["fullPage"]?.boolValue != true { - printParameters["paperWidth"] = 11 - printParameters["paperHeight"] = 8.5 - } let response = try command( "Page.printToPDF", parameters: printParameters, @@ -779,7 +775,7 @@ final class LinuxBrowserSession: @unchecked Sendable { } private func evaluate(_ body: String, input: [String: Any] = [:], timeout: TimeInterval = 10) throws -> JSONValue { - _ = timeout // The dedicated DevTools socket has a bounded 125-second timeout. + let timeoutMilliseconds = Int32(min(125_000, max(1, ceil(timeout * 1_000)))) let inputData = try JSONSerialization.data(withJSONObject: input, options: [.sortedKeys]) guard let inputJSON = String(data: inputData, encoding: .utf8) else { throw CDPError.invalidResponse("input encoding") @@ -796,13 +792,17 @@ final class LinuxBrowserSession: @unchecked Sendable { \(agentEvaluationBody(body)) })() """ - let response = try command("Runtime.evaluate", parameters: [ - "expression": expression, - "awaitPromise": true, - "returnByValue": true, - "userGesture": true, - "contextId": try isolatedExecutionContextID(), - ]) + let response = try command( + "Runtime.evaluate", + parameters: [ + "expression": expression, + "awaitPromise": true, + "returnByValue": true, + "userGesture": true, + "contextId": try isolatedExecutionContextID(), + ], + timeoutMilliseconds: timeoutMilliseconds + ) if let exception = response["exceptionDetails"] as? [String: Any] { throw CDPError.commandFailed(exception["text"] as? String ?? String(describing: exception)) } diff --git a/apps/headless/Sources/HeadlessProtocol/CLI.swift b/apps/headless/Sources/HeadlessProtocol/CLI.swift index b2c8b3d..39dd744 100644 --- a/apps/headless/Sources/HeadlessProtocol/CLI.swift +++ b/apps/headless/Sources/HeadlessProtocol/CLI.swift @@ -631,11 +631,11 @@ Core workflow: headless start headless session create qa headless --session qa visit localhost:3000/designers/dashboard - headless --session qa inspect --context summary --task "click Continue" --json + headless --session qa inspect --context summary --task "click Continue" headless --session qa tour --full-page headless --session qa click --role button --name Continue headless --session qa wait --settled - headless --session qa capture-info --json + headless --session qa capture-info Commands: start | status | stop | runtime @@ -674,7 +674,6 @@ Commands: Global options: --session NAME target a named browser session - --json emit one JSON object on stdout -- stop parsing global options; quote multi-word fill values """ diff --git a/apps/headless/Sources/HeadlessProtocol/Protocol.swift b/apps/headless/Sources/HeadlessProtocol/Protocol.swift index 3c78359..9f64177 100644 --- a/apps/headless/Sources/HeadlessProtocol/Protocol.swift +++ b/apps/headless/Sources/HeadlessProtocol/Protocol.swift @@ -701,14 +701,4 @@ public extension JSONValue { } } - var foundationObject: Any { - switch self { - case .string(let value): return value - case .number(let value): return value - case .bool(let value): return value - case .object(let value): return value.mapValues(\.foundationObject) - case .array(let value): return value.map(\.foundationObject) - case .null: return NSNull() - } - } } diff --git a/apps/headless/Sources/HeadlessProtocol/ScreenshotSeries.swift b/apps/headless/Sources/HeadlessProtocol/ScreenshotSeries.swift index e7c6bae..0cd8c82 100644 --- a/apps/headless/Sources/HeadlessProtocol/ScreenshotSeries.swift +++ b/apps/headless/Sources/HeadlessProtocol/ScreenshotSeries.swift @@ -71,10 +71,6 @@ public func parseScreenshotSeriesPlan(_ plan: JSONValue) throws -> ScreenshotSer ) } -public func screenshotSeriesPoints(from plan: JSONValue) throws -> [ScreenshotSeriesPoint] { - try parseScreenshotSeriesPlan(plan).points -} - public func screenshotSeriesPrefix(parameters: [String: JSONValue], mode: String) throws -> String { if let requested = parameters["outputPrefix"]?.stringValue { try validateArtifactPrefix(requested) diff --git a/apps/headless/Tests/HeadlessProtocolTests/ProtocolTests.swift b/apps/headless/Tests/HeadlessProtocolTests/ProtocolTests.swift index 39ad035..30894db 100644 --- a/apps/headless/Tests/HeadlessProtocolTests/ProtocolTests.swift +++ b/apps/headless/Tests/HeadlessProtocolTests/ProtocolTests.swift @@ -419,6 +419,7 @@ struct ProtocolTests { invocation.request?.parameters["url"] == .string("http://localhost:3000/designers/dashboard"), "visit URL should normalize" ) + try expect(!agentHelp.contains("--json"), "the no-op --json compatibility flag should stay out of help") } static func cliFillPreservesLiteralValue() throws { diff --git a/docs/roadmap/improvements-backlog.md b/docs/roadmap/improvements-backlog.md index 4c12254..12718d8 100644 --- a/docs/roadmap/improvements-backlog.md +++ b/docs/roadmap/improvements-backlog.md @@ -178,7 +178,7 @@ characters, local hosts, and numeric bounds have one shared definition; the CLI enforces the validator's scroll and network ranges; and protocol coverage parses both JavaScript extension sets and requires exact equality with Swift. -**B4. Dead code removal.** ([#24](https://github.com/LockInTime/headless/issues/24)) `screenshotSeriesPoints(from:)` +**B4. Dead code removal.** ([#24](https://github.com/LockInTime/headless/issues/24)) ~~`screenshotSeriesPoints(from:)` (`HP/ScreenshotSeries.swift:74-76`), `JSONValue.foundationObject` (`HP/Protocol.swift:670-679`), discarded `timeout` param (`LinuxHost/BrowserProcess.swift:777-778`) — either honor it (tour expects @@ -186,7 +186,10 @@ parses both JavaScript extension sets and requires exact equality with Swift. (`BrowserProcess.swift:483-486`), effectively-no-op `--json` flag (`HeadlessCLI/main.swift:145`) — implement human-readable output or remove the flag from help, `jsonValue(from:)` duplicate -(`AgentBridge.swift:428-441`). +(`AgentBridge.swift:428-441`).~~ **Done:** removed the unused screenshot and +Foundation conversion paths, made both hosts share `JSONValue.foundationValue`, +honored the bounded Linux evaluation timeout, deleted the unreachable PDF +branch, and hid the backward-compatible no-op `--json` parser flag from help. **B5. `pruneToBudget` quality.** ([#25](https://github.com/LockInTime/headless/issues/25)) Hand-rolled 2-pass fixed point (`HP/AgentRuntime.swift:348-352`), O(n²) re-encoding per trim, pop-largest-