fix(blocker-int-1): replace NoopBusGateway with real MCPBusGatewayAdapter

ConfirmingToolDispatcher emits tool-call events through `BusGateway`. The
prior `NoopBusGateway` (AppDelegate.swift:503 + dead struct at :1894-1904)
silently dropped every emission, so the HUD's chat-panel ToolCallCard
never rendered a Running / Completed transition for any tool dispatch.
This is the canonical "test mock and prod stub diverge in the same
direction" pattern called out in `.planning/audit-2026-05-03/tests-audit.md`
§6: SpyBus in MCPRuntimeWiringTests counted invocations; production
shipped with empty bodies — both satisfied the protocol; only the latter
was load-bearing in production.

The replacement `MCPBusGatewayAdapter` (App/MCPBusGatewayAdapter.swift):
- Conforms to `BusGateway`, forwards to `OutboundBatcher.flushAndSend`
  with `.toolCallStart` / `.toolCallEnd` envelopes.
- `updateArgsPreview` re-emits `.toolCallStart` with the same id; the
  HUD's `upsertToolCall` (webview/packages/hud/src/bus/client.ts) patches
  the existing card by id, transitioning awaiting-approval → running with
  the post-approval sanitized args.
- `String → UUID` mapping via SHA256-derived RFC 4122 v5-shape UUID, so
  `emitToolCallStart(id="toolu_01X")` and `emitToolCallEnd(id="toolu_01X")`
  produce IDENTICAL UUIDs for HUD reconciliation across start/update/end.
  The dispatcher protocol uses `String` (CR-03 in ConfirmingToolDispatcher.swift
  — Anthropic toolu_01ABCD… / Ollama free-form, never UUID); the bus
  protocol uses `UUID` (Phase 2 closed schema). Bridge is one-way; the
  audit-trail correlation by String id already lives at
  ReplayingToolResultObserver.
- Late binding via `@Sendable () async -> OutboundBatcher?` resolver:
  the batcher is built in installAgent (step 13), AFTER mcpInstallTask
  captures the gateway in step 10. The closure resolves it at emit-time;
  if the batcher is nil (early window before installAgent finishes,
  or shutdown), emissions drop silently — the replay log path still
  records the call so audit isn't lost.

Tests: App/Tests/AppTests/MCPBusGatewayAdapterTests.swift (+5):
- stableUUID determinism (same input → same UUID)
- stableUUID distinguishes different ids
- emitToolCallStart reaches the sink with right name/argsPreview/id
- emitToolCallEnd id matches the start id (reconciliation invariant)
- updateArgsPreview re-emits .toolCallStart with same id, updated args
- nil batcher drops events silently (no trap)

The recording sink + OutboundBatcher path the test exercises is the
same path production uses end-to-end through to WebviewBridge.send.
The remaining seam (sink → JS-side store) is already covered by
RealWKWebViewIntegrationTests + webview/hud streaming.test.tsx D5/D6/D7.

App build clean. Bus tests 58/58 (was 57/58 before F-A2-01 fix).
MCP tests 85/85 unchanged. All 5 boundary gates green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
james.maesandClaude Opus 4.7 committed 2026-05-04 11:47:47 -05:00
1 parent 6f6617e67b
commit ad93dac665
4 files changed
+281 -15

No files matched your search

+10 -15
View File
@@ -500,7 +500,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let bundleURL = Bundle.main.bundleURL
mcpInstallTask = Task { @MainActor [weak self] in
guard let self = self, let channel = self.orchToReplayChannel else { return }
let busAdapter = NoopBusGateway() // CR-02: orchestrator wiring (later plan) replaces with real bus adapter.
// BLOCKER-INT-1 fix: real BusGateway that forwards tool-call
// events to the HUD via OutboundBatcher. The batcher is
// constructed later (step 13 in installAgent) so the adapter
// resolves it lazily via a MainActor-isolated closure. Until
// the batcher exists, emissions are dropped (the
// ReplayingToolResultObserver still records the call so the
// audit trail isn't lost).
let busAdapter = MCPBusGatewayAdapter(resolveBatcher: { [weak self] in
await MainActor.run { [weak self] in self?.outboundBatcher }
})
do {
let runtime = try await MCPRuntimeWiring.build(
bundleURL: bundleURL,
@@ -1889,20 +1898,6 @@ private final class BridgeNavigationDelegate: NSObject, WKNavigationDelegate {
}
}
// MARK: - Bus gateway placeholder
/// CR-02 (REVIEW 05): pre-orchestrator no-op BusGateway. Phase 6 / 7
/// orchestrator wiring replaces this with a real adapter that translates
/// the dispatcher's bus events into `BusOutbound.toolCallStart` /
/// `toolCallEnd` cases on the WebviewBridge. Until then, the dispatcher
/// chain still composes (BusGateway must be non-nil for some call sites)
/// but emits into a sink that drops events on the floor.
struct NoopBusGateway: BusGateway {
func emitToolCallStart(toolUseId: String, name: String, argsPreview: String) async {}
func updateArgsPreview(toolUseId: String, name: String, argsPreview: String) async {}
func emitToolCallEnd(toolUseId: String, name: String, ok: Bool, previewOrError: String) async {}
}
// MARK: - Voice subsystem adapters (Plan 06-05)
//
// These thin adapters bridge the Voice package protocol seams to App-target types.
+96
View File
@@ -0,0 +1,96 @@
import Foundation
import CryptoKit
import Bus // OutboundBatcher, BusOutbound
import JarvisMCP // BusGateway
/// BLOCKER-INT-1 fix: real `BusGateway` implementation that forwards
/// `ConfirmingToolDispatcher`'s tool-call events to the HUD via
/// `OutboundBatcher`. Replaces the prior `NoopBusGateway` in `AppDelegate.swift`
/// (line ~503) which silently dropped every tool-call card.
///
/// Wire mapping:
/// - `emitToolCallStart(toolUseId:name:argsPreview:)` →
/// `BusOutbound.toolCallStart(id: stableUUID(toolUseId), name:, argsPreview:)`
/// - `updateArgsPreview(toolUseId:name:argsPreview:)` →
/// second `.toolCallStart` with the SAME id; the HUD's `upsertToolCall`
/// reconciler patches the existing card with the post-approval
/// `argsPreview`. Drift the audit calls out in `streaming.test.tsx D5b`
/// is preserved (whitespace-insensitive parser on JS side).
/// - `emitToolCallEnd(toolUseId:name:ok:previewOrError:)` →
/// `BusOutbound.toolCallEnd(id: stableUUID(toolUseId), ok:, previewOrError:)`
///
/// `BusOutbound.toolCallStart/End` use `UUID` for `id` (Phase 2 closed schema);
/// the dispatcher's protocol uses `String` (Anthropic `toolu_01ABCD…` /
/// Ollama free-form, never UUID — see CR-03 in
/// `ConfirmingToolDispatcher.swift`). We bridge with a deterministic SHA256
/// → UUID derivation so:
/// 1. `emitToolCallStart(id="toolu_01X")` and `emitToolCallEnd(id="toolu_01X")`
/// produce the IDENTICAL `UUID`, allowing the HUD to reconcile by id.
/// 2. The mapping is one-way (we never decode UUID back to original id);
/// the audit-trail correlation across bus / replay / orchestrator
/// already lives at the String layer in `ReplayingToolResultObserver`.
/// 3. Two distinct dispatcher String ids hash to two distinct UUIDs with
/// negligible collision probability (SHA256 truncated to 128 bits).
///
/// Late binding: at the time `mcpInstallTask` captures the gateway (step 10
/// of `applicationWillFinishLaunching`), `OutboundBatcher` is not yet
/// constructed — `installAgent` (step 13) builds it. The gateway uses a
/// `@Sendable` resolver closure so `OutboundBatcher?` is read at emit-time,
/// not capture-time. Until the batcher exists, emissions are dropped (the
/// observer-side replay log still records the call).
public actor MCPBusGatewayAdapter: BusGateway {
private let resolveBatcher: @Sendable () async -> OutboundBatcher?
/// `resolveBatcher` returns the live `OutboundBatcher` (or nil if not
/// yet constructed / app shutting down). Production path: closure
/// hops to MainActor and reads `appDelegate.outboundBatcher`.
public init(resolveBatcher: @escaping @Sendable () async -> OutboundBatcher?) {
self.resolveBatcher = resolveBatcher
}
// MARK: BusGateway
public func emitToolCallStart(toolUseId: String, name: String, argsPreview: String) async {
guard let batcher = await resolveBatcher() else { return }
let uuid = Self.stableUUID(from: toolUseId)
try? await batcher.flushAndSend(.toolCallStart(id: uuid, name: name, argsPreview: argsPreview))
}
public func updateArgsPreview(toolUseId: String, name: String, argsPreview: String) async {
guard let batcher = await resolveBatcher() else { return }
let uuid = Self.stableUUID(from: toolUseId)
// Re-emit toolCallStart with same id; HUD upserts the args field
// by id (see webview/packages/hud/src/bus/client.ts case
// 'toolCallStart' → `store.upsertToolCall(msg.id, …)`).
try? await batcher.flushAndSend(.toolCallStart(id: uuid, name: name, argsPreview: argsPreview))
}
public func emitToolCallEnd(toolUseId: String, name: String, ok: Bool, previewOrError: String) async {
guard let batcher = await resolveBatcher() else { return }
let uuid = Self.stableUUID(from: toolUseId)
try? await batcher.flushAndSend(.toolCallEnd(id: uuid, ok: ok, previewOrError: previewOrError))
}
// MARK: - Stable id derivation
/// Deterministic `String → UUID` derivation: SHA256 of UTF-8 bytes, take
/// the first 16 bytes, set the version (5) and variant (RFC 4122)
/// bits per RFC 4122 §4.4 so the resulting UUID is well-formed. The
/// hash is the load-bearing property — RFC compliance is incidental
/// but makes the values valid `UUID(uuidString:)` round-trips.
static func stableUUID(from toolUseId: String) -> UUID {
let digest = SHA256.hash(data: Data(toolUseId.utf8))
var bytes = [UInt8](digest.prefix(16))
// RFC 4122 §4.4: set version (top nibble of byte 6) to 5
bytes[6] = (bytes[6] & 0x0F) | 0x50
// RFC 4122 §4.4: set variant (top two bits of byte 8) to 10
bytes[8] = (bytes[8] & 0x3F) | 0x80
let tuple = (
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5], bytes[6], bytes[7],
bytes[8], bytes[9], bytes[10], bytes[11],
bytes[12], bytes[13], bytes[14], bytes[15]
)
return UUID(uuid: tuple)
}
}
@@ -0,0 +1,167 @@
// MCPBusGatewayAdapterTests.swift
//
// BLOCKER-INT-1 closure proof: assert that `MCPBusGatewayAdapter` (the
// real BusGateway production code; replaces NoopBusGateway at
// AppDelegate.swift:503) forwards tool-call events through `OutboundBatcher`
// to its `Sink` so the HUD's chat-panel ToolCallCard receives the wire
// `BusOutbound.toolCallStart` / `.toolCallEnd` envelopes the JS-side
// reconciler in `webview/packages/hud/src/bus/client.ts` consumes.
//
// The previous `NoopBusGateway` was the canonical "test mock and prod
// stub diverge in the same direction" pattern called out in
// `.planning/audit-2026-05-03/tests-audit.md` §6: SpyBus in
// MCPRuntimeWiringTests.swift counted invocations + production used
// NoopBusGateway with empty bodies. Both satisfied the protocol; only
// the latter shipped — and silently dropped every tool-call card.
//
// These tests assert the production-side behavior of the replacement:
// the events DO reach the sink, with stable ids that allow the HUD to
// reconcile start / update / end by id.
import XCTest
import Foundation
import Bus
@testable import Jarvis // MCPBusGatewayAdapter is internal to the App target
@MainActor
final class MCPBusGatewayAdapterTests: XCTestCase {
/// Recording `OutboundBatcher.Sink` — captures every `BusOutbound`
/// envelope that reaches the JS-call boundary. `@MainActor` matches
/// the protocol's `sendRaw` isolation.
final class RecordingSink: OutboundBatcher.Sink, @unchecked Sendable {
var sent: [BusOutbound] = []
@MainActor func sendRaw(_ msg: BusOutbound) async throws {
sent.append(msg)
}
}
// MARK: - Stable id derivation
/// `stableUUID(from:)` is deterministic — same input string always
/// produces the same UUID. Load-bearing for HUD reconciliation:
/// `emitToolCallStart(id="toolu_X")` followed by
/// `emitToolCallEnd(id="toolu_X")` MUST produce matching UUIDs so
/// the JS-side `upsertToolCall` patches the existing card rather
/// than orphaning the start emission.
func test_stableUUID_isDeterministic() {
let a = MCPBusGatewayAdapter.stableUUID(from: "toolu_01ABCDEFGH")
let b = MCPBusGatewayAdapter.stableUUID(from: "toolu_01ABCDEFGH")
XCTAssertEqual(a, b, "same input must produce same UUID across calls")
}
/// Distinct dispatcher String ids must produce distinct UUIDs so two
/// concurrent tool calls don't collide in the HUD.
func test_stableUUID_distinguishesDifferentIds() {
let a = MCPBusGatewayAdapter.stableUUID(from: "toolu_01ABC")
let b = MCPBusGatewayAdapter.stableUUID(from: "toolu_01XYZ")
XCTAssertNotEqual(a, b)
}
// MARK: - Event flow: gateway → batcher → sink
/// `emitToolCallStart` produces exactly one `.toolCallStart` envelope at
/// the sink, carrying the deterministic UUID + the name + argsPreview.
/// This is the base case BLOCKER-INT-1 was breaking: NoopBusGateway
/// dropped this entirely.
func test_emitToolCallStart_reachesSink() async throws {
let sink = RecordingSink()
let batcher = OutboundBatcher(sink: sink, windowMillis: 5)
let adapter = MCPBusGatewayAdapter(resolveBatcher: { batcher })
await adapter.emitToolCallStart(
toolUseId: "toolu_01TEST",
name: "run_applescript",
argsPreview: "{\"awaitingApproval\":true}"
)
XCTAssertEqual(sink.sent.count, 1, "exactly one envelope at the sink")
guard case let .toolCallStart(id, name, argsPreview) = sink.sent[0] else {
XCTFail("expected .toolCallStart, got \(sink.sent[0])")
return
}
XCTAssertEqual(id, MCPBusGatewayAdapter.stableUUID(from: "toolu_01TEST"))
XCTAssertEqual(name, "run_applescript")
XCTAssertEqual(argsPreview, "{\"awaitingApproval\":true}")
}
/// `emitToolCallEnd` produces a `.toolCallEnd` whose UUID matches the
/// UUID `emitToolCallStart` would have produced for the same dispatcher
/// String id. HUD reconciliation depends on this equality.
func test_emitToolCallEnd_idMatchesStart() async throws {
let sink = RecordingSink()
let batcher = OutboundBatcher(sink: sink, windowMillis: 5)
let adapter = MCPBusGatewayAdapter(resolveBatcher: { batcher })
await adapter.emitToolCallStart(toolUseId: "toolu_01ID", name: "get_time", argsPreview: "{}")
await adapter.emitToolCallEnd(
toolUseId: "toolu_01ID",
name: "get_time",
ok: true,
previewOrError: "\"2026-05-04T00:00:00Z\""
)
XCTAssertEqual(sink.sent.count, 2)
guard
case let .toolCallStart(startId, _, _) = sink.sent[0],
case let .toolCallEnd(endId, ok, previewOrError) = sink.sent[1]
else {
XCTFail("expected toolCallStart followed by toolCallEnd")
return
}
XCTAssertEqual(startId, endId, "HUD reconciliation requires matching id across start/end")
XCTAssertTrue(ok)
XCTAssertEqual(previewOrError, "\"2026-05-04T00:00:00Z\"")
}
/// `updateArgsPreview` re-emits a `.toolCallStart` carrying the
/// post-approval sanitized args. The HUD's `upsertToolCall` patches
/// the existing card by id (see streaming.test.tsx D5 / D5b in
/// `webview/packages/hud/`), transitioning awaiting-approval → running
/// while the args render the real post-approval payload.
func test_updateArgsPreview_reEmitsStartWithSameId() async throws {
let sink = RecordingSink()
let batcher = OutboundBatcher(sink: sink, windowMillis: 5)
let adapter = MCPBusGatewayAdapter(resolveBatcher: { batcher })
await adapter.emitToolCallStart(
toolUseId: "toolu_01APPLE",
name: "run_applescript",
argsPreview: "{\"awaitingApproval\":true}"
)
await adapter.updateArgsPreview(
toolUseId: "toolu_01APPLE",
name: "run_applescript",
argsPreview: "{\"script\":\"return 1\"}"
)
XCTAssertEqual(sink.sent.count, 2)
guard
case let .toolCallStart(id1, _, args1) = sink.sent[0],
case let .toolCallStart(id2, _, args2) = sink.sent[1]
else {
XCTFail("expected two .toolCallStart emissions")
return
}
XCTAssertEqual(id1, id2, "update must carry same id so HUD patches the existing card")
XCTAssertEqual(args1, "{\"awaitingApproval\":true}")
XCTAssertEqual(args2, "{\"script\":\"return 1\"}")
}
/// Late binding: when the resolver returns `nil` (batcher not yet
/// constructed in installAgent step 13), the adapter drops the
/// emission silently rather than crashing. The audit-trail
/// correlation lives in `ReplayingToolResultObserver` (separate path).
func test_nilBatcher_dropsEventSilently() async {
let adapter = MCPBusGatewayAdapter(resolveBatcher: { nil })
// No assertion fails — the call must not throw or trap.
await adapter.emitToolCallStart(toolUseId: "toolu_01EARLY", name: "x", argsPreview: "{}")
await adapter.updateArgsPreview(toolUseId: "toolu_01EARLY", name: "x", argsPreview: "{}")
await adapter.emitToolCallEnd(
toolUseId: "toolu_01EARLY",
name: "x",
ok: false,
previewOrError: "early"
)
}
}
+8
View File
@@ -38,6 +38,7 @@
66B4B219EF4EC3EBDE5D1A4B /* AppDelegateWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 909BFE54FAE9CA6B878D5625 /* AppDelegateWiringTests.swift */; };
6A2D9DE01C3C53094A820DDF /* JarvisEntitlementProbeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36AC239C99B913A4767C0EE8 /* JarvisEntitlementProbeTests.swift */; };
6AD395C3C083197537B81713 /* AgentCore in Frameworks */ = {isa = PBXBuildFile; productRef = 73CDBE39012C4ECD47291B0E /* AgentCore */; };
70D09A556654E11B140AFBD4 /* MCPBusGatewayAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6639284829C6DB4F69ED47F7 /* MCPBusGatewayAdapterTests.swift */; };
7270A6A54B0DC1510EB9C56A /* WizardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33A1960A6404CD790036C46 /* WizardView.swift */; };
781E23F05F4EBBD3D3CB8E3F /* AppleScriptRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F80410112AAA66BF53BBC28 /* AppleScriptRunner.swift */; };
78AA6D0286502DC0F1F4304D /* HudState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF7206DB98EC02734975536 /* HudState.swift */; };
@@ -76,6 +77,7 @@
C9F3B34208084769106C4B07 /* HudStateCoordinatorBusWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CB8B7693CEA625D7331C3E /* HudStateCoordinatorBusWiringTests.swift */; };
CCA51A998B128C9C03ED0662 /* AppDelegateFrameAttachAdapters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB47EBED48DF63272C6D77F /* AppDelegateFrameAttachAdapters.swift */; };
CD22CB0656265A2623BA7FD0 /* AnthropicKeyValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39DD53A4E2CCED58C85B63FC /* AnthropicKeyValidatorTests.swift */; };
CD2C5779101FE0384611ECCC /* MCPBusGatewayAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C42ACA7552D565D9F198399 /* MCPBusGatewayAdapter.swift */; };
CFBEF67D687A1227C9C2A6BD /* HUDBannerCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98DF77D7944DD23250A9AA97 /* HUDBannerCoordinatorTests.swift */; };
CFFC4AB8D77907C477B1E947 /* JarvisMCP in Frameworks */ = {isa = PBXBuildFile; productRef = DE970D2B3CA1AB0097A120D3 /* JarvisMCP */; };
D0F74EFBDD6B9FDD0FC15A80 /* JarvisMCP in Frameworks */ = {isa = PBXBuildFile; productRef = 0AA20E2847670C06A4E77816 /* JarvisMCP */; };
@@ -148,6 +150,7 @@
2468510EF34D9D8D56F5E3B9 /* MenuBarIconControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarIconControllerTests.swift; sourceTree = "<group>"; };
2857325F1F38B436FFA8AD8B /* mcp-time.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "mcp-time.app"; sourceTree = BUILT_PRODUCTS_DIR; };
288436CCFD821C16F6C5995E /* Vision */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Vision; path = packages/Vision; sourceTree = SOURCE_ROOT; };
2C42ACA7552D565D9F198399 /* MCPBusGatewayAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCPBusGatewayAdapter.swift; sourceTree = "<group>"; };
2E9C7A0B14B3AE5C9100CACF /* Config */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Config; path = packages/Config; sourceTree = SOURCE_ROOT; };
3527AF52C1E81BAC4EF70766 /* JarvisHUDPanelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JarvisHUDPanelTests.swift; sourceTree = "<group>"; };
3577EF13419964F207DF4A37 /* VoiceOrchestratorAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceOrchestratorAdapterTests.swift; sourceTree = "<group>"; };
@@ -171,6 +174,7 @@
604976A6CA41878D67AF7B71 /* CAAnimationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAAnimationFactory.swift; sourceTree = "<group>"; };
639B5F162E847B0CFC679E30 /* Bus */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Bus; path = packages/Bus; sourceTree = SOURCE_ROOT; };
6569DC91C2ACEB0E1396E1B8 /* HudStateBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HudStateBridge.swift; sourceTree = "<group>"; };
6639284829C6DB4F69ED47F7 /* MCPBusGatewayAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCPBusGatewayAdapterTests.swift; sourceTree = "<group>"; };
6784683F2262D8AFA439668D /* JarvisAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JarvisAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
718CFD37BC47D00448B86C8D /* VoiceTTSAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceTTSAdapter.swift; sourceTree = "<group>"; };
74FC22D7D34AB5E6061959D7 /* WizardStageHotkeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardStageHotkeyView.swift; sourceTree = "<group>"; };
@@ -297,6 +301,7 @@
C1517F151F3BA96803143692 /* AppDelegate.swift */,
467AE2D19D58325D227E2F89 /* Assets.xcassets */,
90D238B5CF4BD07EB47F84F0 /* JarvisApp.swift */,
2C42ACA7552D565D9F198399 /* MCPBusGatewayAdapter.swift */,
F76520C565169D7547EE9608 /* HUD */,
EE585F62F46C659EDFC48DE7 /* MCP */,
35E7CF3C8F80086AD447480E /* MenuBar */,
@@ -358,6 +363,7 @@
8B7E85856B679CE0788D38DB /* HudStateCoordinatorTests.swift */,
510B6A716A10A8B7CEEC1D3F /* HudStateEnumTests.swift */,
3527AF52C1E81BAC4EF70766 /* JarvisHUDPanelTests.swift */,
6639284829C6DB4F69ED47F7 /* MCPBusGatewayAdapterTests.swift */,
0B5B16BDAC02919861AA4B0B /* MCPRuntimeWiringTests.swift */,
2468510EF34D9D8D56F5E3B9 /* MenuBarIconControllerTests.swift */,
3577EF13419964F207DF4A37 /* VoiceOrchestratorAdapterTests.swift */,
@@ -1258,6 +1264,7 @@
8E4590089A7955545E2146D3 /* HudStateCoordinatorTests.swift in Sources */,
BBD8BBC82E0D3C57E0DB4ED0 /* HudStateEnumTests.swift in Sources */,
461A5FACC5A3C2025FB0A233 /* JarvisHUDPanelTests.swift in Sources */,
70D09A556654E11B140AFBD4 /* MCPBusGatewayAdapterTests.swift in Sources */,
5AD556F5095852328E72BE36 /* MCPRuntimeWiringTests.swift in Sources */,
5B5175F8D07766B3394DF19C /* MenuBarIconControllerTests.swift in Sources */,
B5416071D18E6C124365A8EC /* VoiceOrchestratorAdapterTests.swift in Sources */,
@@ -1299,6 +1306,7 @@
896984D88C412595C0F6F97E /* HudStateIntent.swift in Sources */,
526BDF4D6BC5714809B2E937 /* JarvisApp.swift in Sources */,
BEFC509502C7940A21001F9F /* JarvisHUDPanel.swift in Sources */,
CD2C5779101FE0384611ECCC /* MCPBusGatewayAdapter.swift in Sources */,
8EF7109838AD91E95506BD1B /* MCPRuntimeWiring.swift in Sources */,
A20E24D0AB73FFBBE4AB9B9B /* MenuBarContextMenu.swift in Sources */,
9933987292A1035BB048D0B9 /* MenuBarIconController.swift in Sources */,