Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 1 addition & 15 deletions apps/headless/Host/AgentBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -417,17 +417,3 @@ private func onMain<T>(_ 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")
}
}
26 changes: 13 additions & 13 deletions apps/headless/LinuxHost/BrowserProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand All @@ -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))
}
Expand Down
5 changes: 2 additions & 3 deletions apps/headless/Sources/HeadlessProtocol/CLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
"""

Expand Down
10 changes: 0 additions & 10 deletions apps/headless/Sources/HeadlessProtocol/Protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions docs/roadmap/improvements-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,18 @@ 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
65 s) or delete it, unreachable non-fullPage PDF branch
(`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-
Expand Down
Loading