diff --git a/Sources/NucleicCore/AppStore.swift b/Sources/NucleicCore/AppStore.swift index 0afab1f1..7b464378 100644 --- a/Sources/NucleicCore/AppStore.swift +++ b/Sources/NucleicCore/AppStore.swift @@ -13546,16 +13546,21 @@ extension AppStore: SyncHostBridge { } /// The `nucleic_monitor` event sink for a session: deliver a fired monitor's output back into - /// the session as a fresh user turn (the cross-turn re-invocation the native `Monitor` can't do + /// the session as a fresh turn (the cross-turn re-invocation the native `Monitor` can't do /// under Nucleic's single-shot turn model). Injection is an ordinary `sendInput` — it starts a - /// turn when the session is idle, queues (and coalesces) when one is in flight — and is - /// transcript-visible, so every wake-up is auditable. Supplied unconditionally; the backend only - /// calls it for a monitor the agent itself armed. + /// turn when the session is idle, queues (and coalesces) when one is in flight. The agent sees + /// the wake-up as user-role text, but it is *not* echoed to the transcript as a user message: + /// nobody typed it, and a wall of monitored command output masquerading as the user's own bubble + /// misreads the conversation badly. A muted note carries the same payload instead, so every + /// wake-up stays auditable while reading as what it is — machine output. Supplied + /// unconditionally; the backend only calls it for a monitor the agent itself armed. func monitorEventSink(for sessionID: SessionID) -> MonitorEventSink { { [weak self] description, event in guard let self, let controller = self.controllers[sessionID] else { return } let header = "[Monitor: \(description)]" - try? await controller.sendInput(AgentInput(text: "\(header)\n\(event)")) + await controller.note("\(header)\n\(event)", icon: "binoculars") + try? await controller.sendInput( + AgentInput(text: "\(header)\n\(event)"), echoToTranscript: false) } } diff --git a/Sources/NucleicCore/Backend.swift b/Sources/NucleicCore/Backend.swift index 0236044d..a0cdde76 100644 --- a/Sources/NucleicCore/Backend.swift +++ b/Sources/NucleicCore/Backend.swift @@ -410,7 +410,9 @@ public typealias OrchestraAskSupervisorHandler = /// single-shot turn model (each turn is one foreground `claude -p` process that exits at end of /// turn, with no harness loop to wake the agent later). The monitor's watch is owned by the /// backend across turns; when its command emits output, the backend calls this sink and the host -/// injects the event via `SessionController.sendInput`, starting (or queuing) a new turn. +/// injects the event via `SessionController.sendInput`, starting (or queuing) a new turn. The +/// injection is silent (`echoToTranscript: false`) and paired with a muted note, so the wake-up +/// stays auditable without impersonating a message the user typed. /// `(description, event)` — `description` labels the watch; `event` is the coalesced output lines. public typealias MonitorEventSink = @MainActor @Sendable (_ description: String, _ event: String) async -> Void