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
13 changes: 13 additions & 0 deletions .cursor/rules/verify-before-claiming-fixed.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
description: Prove a fix with the failing path before saying it works
alwaysApply: true
---

# Verify before claiming a fix

Do not treat a code change as fixed because it compiles or because the new logic looks right.

- Read the live logs or the failing output first.
- Add or run a test that fails on the broken behavior and passes after the change.
- Run that test. If the bug is in a running app, build the checkout that actually launches and exercise that path.
- Say what was verified and what was not.
8 changes: 4 additions & 4 deletions packages/HostUI/Sources/HostUI/HostUIMarkdown.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import SwiftUI

/// Shared markdown + `$profile` token coloring for HostUI message bodies.
public enum HostUIMarkdown {
/// Dark green for `$handle` and `[Derrick:handle]`. Darker than system green on white bubbles.
public static let profileTokenColor = Color(red: 0.0, green: 0.42, blue: 0.18)
/// Medium green for `$handle` and `[Derrick:$handle]` on white bubbles.
public static let profileTokenColor = Color(red: 0.18, green: 0.62, blue: 0.32)

public static func attributed(_ text: String) -> AttributedString {
var options = AttributedString.MarkdownParsingOptions()
Expand All @@ -16,7 +16,7 @@ public enum HostUIMarkdown {
return attributed
}

/// Paints `$handle` and `[Derrick:handle]` on already-rendered text. Later copies of the same token are included.
/// Paints `$handle` and `[Derrick:$handle]` on already-rendered text. Later copies of the same token are included.
public static func colorProfileTokens(_ attributed: inout AttributedString) {
let source = String(attributed.characters)
var search = attributed.startIndex
Expand All @@ -29,7 +29,7 @@ public enum HostUIMarkdown {
}
}

/// Plain text with `$handle` and `[Derrick:handle]` in `HostUIMarkdown.profileTokenColor`.
/// Plain text with `$handle` and `[Derrick:$handle]` in `HostUIMarkdown.profileTokenColor`.
public struct HostUIProfileTokenText: View {
private let text: String
private let base: Color
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ public struct HostUIComposer: View {
id: "wx",
sender: "derrick",
body: """
[Derrick:orchestrator] Searching the web…
[Derrick:$orchestrator] Searching the web…

In **Northvale, NJ 07647**, Weather Underground reported **62°F** at **7:27 AM EDT on September 19, 2026**. Its listed daily temp **71°F / 63°F**.

Expand Down
2 changes: 1 addition & 1 deletion packages/HostUI/Sources/HostUI/HostUINodeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ private struct HostUIInboxPreviewHost: View {
id: "r",
sender: "derrick",
body: """
[Derrick:orchestrator] Searching the web…
[Derrick:$orchestrator] Searching the web…

In **Northvale, NJ 07647**, Weather Underground reported **62°F** at **7:27 AM EDT on September 19, 2026**. Its listed daily temp **71°F / 63°F**.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ import Testing

@MainActor
@Suite struct HostUIProfileTokenColorTests {
@Test func dollarHandleAndSenderPrefixShareDarkGreen() {
let text = "$orchestrator then [Derrick:developer] done"
@Test func dollarHandleAndSenderPrefixShareMediumGreen() {
let text = "$orchestrator then [Derrick:$developer] done"
let attributed = HostUIMarkdown.attributed(text)
let snippets = attributed.runs.compactMap { run -> String? in
guard attributed[run.range].foregroundColor == HostUIMarkdown.profileTokenColor else {
return nil
}
return String(attributed[run.range].characters)
}
#expect(snippets == ["$orchestrator", "[Derrick:developer]"])
#expect(snippets == ["$orchestrator", "[Derrick:$developer]"])
}

@Test func plainPreviewKeepsTheSameGreenOnTheSenderPrefix() {
let attributed = HostUIProfileTokenText.attributed(
"[Derrick:orchestrator] Searching the web…",
"[Derrick:$orchestrator] Searching the web…",
base: .secondary
)
let green = attributed.runs.compactMap { run -> String? in
Expand All @@ -26,6 +26,6 @@ import Testing
}
return String(attributed[run.range].characters)
}
#expect(green == ["[Derrick:orchestrator]"])
#expect(green == ["[Derrick:$orchestrator]"])
}
}
18 changes: 12 additions & 6 deletions packages/Structure/Sources/AgentRuntime/AgentProfile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,19 @@ public enum AgentProfileTokenParser {
}
}

/// Ranges of `$shortName` tokens and the full `[Derrick:handle]` sender prefix for UI highlighting.
/// Ranges of `$shortName` tokens and the full `[Derrick:$handle]` sender prefix for UI highlighting.
public enum AgentProfileTokenHighlight {
public static func ranges(
in text: String,
productName: String = DerrickAppSupport.hostAppProductName
) -> [Range<String.Index>] {
var found: [Range<String.Index>] = []
found.append(contentsOf: dollarHandleRanges(in: text))
found.append(contentsOf: productPrefixedHandleRanges(in: text, productName: productName))
return found.sorted { $0.lowerBound < $1.lowerBound }
let prefixed = productPrefixedHandleRanges(in: text, productName: productName)
let dollars = dollarHandleRanges(in: text).filter { dollar in
!prefixed.contains { prefix in
prefix.lowerBound <= dollar.lowerBound && dollar.upperBound <= prefix.upperBound
}
}
return (dollars + prefixed).sorted { $0.lowerBound < $1.lowerBound }
}

public static func nsRanges(
Expand Down Expand Up @@ -195,7 +198,10 @@ public enum AgentProfileTokenHighlight {
let prefix = text.range(of: needle, range: searchFrom..<text.endIndex) {
let handleStart = prefix.upperBound
guard let close = text[handleStart...].firstIndex(of: "]") else { break }
let handle = String(text[handleStart..<close])
var handle = String(text[handleStart..<close])
if handle.hasPrefix("$") {
handle = String(handle.dropFirst())
}
if isHighlightableHandle(handle) {
let tokenEnd = text.index(after: close)
ranges.append(prefix.lowerBound..<tokenEnd)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,37 @@ public enum DerrickDaemonHygiene: Sendable {
return evictedAny || !hasHealthyExpectedDaemon
}

/// Ordered startup actions for the session LaunchAgent.
///
/// The Service Management job and the session job both claim
/// `VUSK4B2YKQ.derrick.shared.daemon`. A crash-looping Service Management job
/// keeps that port with `active = 0`. Registering it again before the session
/// agent is installed puts the port back in that state.
public enum DaemonRegistrationStep: String, Equatable, Sendable {
case unregisterServiceManagement
case bootoutLaunchdJobs
case installSessionAgent
case kickstartSessionAgent
}

public static func registrationSteps(
sessionJobLoaded: Bool,
sessionProgramStale: Bool,
serviceManagementOwnsMachEndpoint: Bool
) -> [DaemonRegistrationStep] {
let sessionNeedsRepair = !sessionJobLoaded || sessionProgramStale
guard sessionNeedsRepair else {
return [.kickstartSessionAgent]
}
var steps: [DaemonRegistrationStep] = []
if serviceManagementOwnsMachEndpoint {
steps.append(.unregisterServiceManagement)
}
steps.append(.bootoutLaunchdJobs)
steps.append(.installSessionAgent)
return steps
}

/// Extra processes of the expected binary steal the Mach service and lock SQLite.
/// Keep the newest; evict the rest.
public static func duplicateExpectedDaemonPIDsToEvict(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public enum ConnectorMentionParser: Sendable {
if handle.isEmpty {
return botReplyPrefix
}
return "[\(DerrickAppSupport.hostAppProductName):\(handle)]"
return "[\(DerrickAppSupport.hostAppProductName):$\(handle)]"
}

/// Reply under the inbound message, or stay in an existing thread.
Expand Down Expand Up @@ -160,7 +160,11 @@ public enum ConnectorMentionParser: Sendable {
else {
return nil
}
return AgentProfileHandle.normalize(String(trimmed[prefix.endIndex..<close]))
var handle = String(trimmed[prefix.endIndex..<close])
if handle.hasPrefix("$") {
handle = String(handle.dropFirst())
}
return AgentProfileHandle.normalize(handle)
}

/// Newest prior talk-to `$shortName` or `[Derrick:handle]` in a thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,13 @@ import Testing
let inboundTokens = AgentProfileTokenHighlight.ranges(in: inbound).map { String(inbound[$0]) }
#expect(inboundTokens == ["$orchestrator"])

let outbound = "[Derrick:developer] Fixed the build."
let outbound = "[Derrick:$developer] Fixed the build."
let outboundTokens = AgentProfileTokenHighlight.ranges(in: outbound).map { String(outbound[$0]) }
#expect(outboundTokens == ["[Derrick:developer]"])
#expect(outboundTokens == ["[Derrick:$developer]"])

let legacy = "[Derrick:developer] Fixed the build."
let legacyTokens = AgentProfileTokenHighlight.ranges(in: legacy).map { String(legacy[$0]) }
#expect(legacyTokens == ["[Derrick:developer]"])

let ignored = AgentProfileTokenHighlight.ranges(in: "price is $100")
#expect(ignored.isEmpty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,38 @@ import Testing
)
}

@Test func daemonRegistrationReleasesServiceManagementBeforeSessionInstall() throws {
let repair = DerrickDaemonHygiene.registrationSteps(
sessionJobLoaded: false,
sessionProgramStale: true,
serviceManagementOwnsMachEndpoint: true
)
#expect(repair == [
.unregisterServiceManagement,
.bootoutLaunchdJobs,
.installSessionAgent,
])
let unregister = try #require(repair.firstIndex(of: .unregisterServiceManagement))
let install = try #require(repair.firstIndex(of: .installSessionAgent))
#expect(unregister < install)
#expect(!repair.contains(.kickstartSessionAgent))

#expect(
DerrickDaemonHygiene.registrationSteps(
sessionJobLoaded: false,
sessionProgramStale: false,
serviceManagementOwnsMachEndpoint: false
) == [.bootoutLaunchdJobs, .installSessionAgent]
)
#expect(
DerrickDaemonHygiene.registrationSteps(
sessionJobLoaded: true,
sessionProgramStale: false,
serviceManagementOwnsMachEndpoint: true
) == [.kickstartSessionAgent]
)
}

@Test func derrickDaemonHygieneEvictsDuplicateExpectedDaemons() {
let expected = "/tmp/Derrick.app/Contents/Library/LoginItems/JobKeepAlive.app/Contents/MacOS/JobKeepAlive"
let evict = DerrickDaemonHygiene.duplicateExpectedDaemonPIDsToEvict(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,17 @@ import Testing
"Done.",
profileHandle: AgentProfileHandle.orchestrator
)
#expect(formatted == "[Derrick:orchestrator] Done.")
#expect(formatted == "[Derrick:$orchestrator] Done.")

let developer = MessagingAgentOutboundFormatter.formatReply(
"Fixed the build.",
profileHandle: AgentProfileHandle.developer
)
#expect(developer == "[Derrick:developer] Fixed the build.")
#expect(developer == "[Derrick:$developer] Fixed the build.")
}

@Test func automatedOutboundEchoDetectsProfilePrefixedReplies() {
#expect(ConnectorMentionParser.isAutomatedOutboundEcho(body: "[Derrick:$orchestrator] hello"))
#expect(ConnectorMentionParser.isAutomatedOutboundEcho(body: "[Derrick:orchestrator] hello"))
#expect(ConnectorMentionParser.isAutomatedOutboundEcho(body: "[Derrick] hello"))
#expect(ConnectorMentionParser.isAutomatedOutboundEcho(body: "plain inbound") == false)
Expand Down Expand Up @@ -154,6 +155,10 @@ import Testing
excludingVendorMessageID: "4"
) == AgentProfileHandle.developer
)
#expect(
ConnectorMentionParser.profileHandle(inMessageBody: "[Derrick:$developer] hi")
== AgentProfileHandle.developer
)
#expect(
ConnectorMentionParser.profileHandle(inMessageBody: "[Derrick:developer] hi")
== AgentProfileHandle.developer
Expand Down
Loading
Loading