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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ Cutting that release is tracked in

### Changed

- Machine-readable capabilities now derive their command and engine inventories
exhaustively from the protocol enums, preventing new commands or engines from
being silently omitted.

- macOS agent startup now opens browser windows without activating Headless or
covering the user's current app. The startup presentation can be configured
persistently or overridden for one launch.
Expand Down
12 changes: 11 additions & 1 deletion apps/headless/Sources/HeadlessProtocol/Capabilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,17 @@ public struct BrowserEngineCapabilities: Sendable {
screenshotClipboard: false
)

public static let all: [BrowserEngineCapabilities] = [.webkit, .chromium]
public static func profile(for engine: BrowserEngineName) -> BrowserEngineCapabilities {
switch engine {
case .webkit: webkit
case .chromium: chromium
}
}

/// Generated from the engine enum so adding an engine cannot silently
/// leave it out of the machine-readable capability document.
public static let all: [BrowserEngineCapabilities] =
BrowserEngineName.allCases.map { BrowserEngineCapabilities.profile(for: $0) }
}

public var currentBrowserEngineCapabilities: BrowserEngineCapabilities {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1213,7 +1213,12 @@ struct ProtocolTests {
engines.count == BrowserEngineName.allCases.count,
"capabilities should contain exactly one profile for every engine"
)
for profile in BrowserEngineCapabilities.all {
try expect(
BrowserEngineCapabilities.all.map(\.engine) == BrowserEngineName.allCases,
"capability profiles should be generated in engine-enum order"
)
for engineName in BrowserEngineName.allCases {
let profile = BrowserEngineCapabilities.profile(for: engineName)
guard case .object(let engine)? = engines[profile.engine.rawValue],
case .array(let rawSupported)? = engine["commands"],
case .array(let rawUnsupported)? = engine["unsupportedCommands"],
Expand Down
8 changes: 6 additions & 2 deletions docs/roadmap/improvements-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,12 @@ oversized line / local-command rejection (`MCP/main.swift:64-66`).~~ **Done:**
`HeadlessMCPTests` drives the built stdio process through every named case and
uses a private local socket server to verify a real browser-command round trip.

**C4. Machine-accurate `capabilities`** ([#32](https://github.com/LockInTime/headless/issues/32)) — generate from `CommandName.allCases`
and the engine matrix (see B6) so agents can trust it.
**C4. Machine-accurate `capabilities`** ([#32](https://github.com/LockInTime/headless/issues/32)) — ~~generate from `CommandName.allCases`
and the engine matrix (see B6) so agents can trust it.~~ **Done:** the document
derives commands from `CommandName.allCases`, engine profiles from
`BrowserEngineName.allCases`, and capture formats and bounds from their runtime
definitions. The protocol suite proves both command partitions are exhaustive
and every declared engine has exactly one profile.

**C5. Harness onboarding [exists: skill content].** ([#33](https://github.com/LockInTime/headless/issues/33)) ~~Root `AGENTS.md` +
`CLAUDE.md`; mirror the skill into `.claude/skills/` so Claude Code
Expand Down