Display Subagent Events

Nucleic-Session: C0D404BC-7C32-4ECC-9F1E-3426BFE53298
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-07 22:52:59 -07:00
co-authored by nucleic
parent 85120ffbe9
commit 1d06140540
@@ -229,6 +229,69 @@ import NucleicProtocol
assertEquivalent(s.events, showLockEvents: false)
}
/// A lock/nvrsion note *tagged* with a subagent's edit nests in that subagent's card instead of
/// leaking into the main transcript; an untagged note (a turn-end release, or a main-agent
/// edit's note) stays at the top level. Also checks the incremental projector agrees across
/// every prefix a routed note stays on its subagent's side of every seal seam.
@Test func taggedNotesNestInSubagentCard() {
let s = Stream()
s.started()
s.user("u0", "delegate an edit")
s.textFinal("m0", "spawning a subagent")
s.toolStart("A", "Task"); s.toolComplete("A", "Task")
s.textFinal("a0", "on it", parent: "A")
// The subagent edits a file; its lock-acquire and nvrsion-land notes carry the edit's id
// (a child of Task A), so they must nest in A's card, not the main transcript.
s.edit("e1", path: "/repo/wt/src/a.swift", parent: "A")
s.note("Lock acquired on src/a.swift.", icon: "lock.fill", lockEvent: true,
lock: NoteLock(state: .acquired, paths: ["src/a.swift"]), toolCallID: "e1")
s.note("nvrsion: landed src/a.swift · abc1234", icon: "checkmark.circle", toolCallID: "e1")
s.result("A", .string("edited"))
s.textFinal("m1", "subagent done")
s.turn()
// The turn-end release is untagged: it lands after A returned, so it stays top level and
// has no *top-level* edit of a.swift to fold onto, so it renders as a standalone row.
s.note("Lock released on src/a.swift.", icon: "lock.open", lockEvent: true,
lock: NoteLock(state: .released, paths: ["src/a.swift"]))
s.textFinal("m2", "landed")
s.turn()
assertEquivalent(s.events)
assertEquivalent(s.events, showLockEvents: false)
let items = TranscriptProjection.build(s.events, showRaw: false, showLockEvents: true)
let topNotes = Self.noteTexts(items) // top level only, no descent
let cardNotes = Self.noteTexts(Self.subagentChildren(items, cardID: "A"))
// The subagent's tagged notes are inside its card
#expect(cardNotes.contains { $0.hasPrefix("Lock acquired on src/a.swift") })
#expect(cardNotes.contains { $0.hasPrefix("nvrsion: landed src/a.swift") })
// and not leaking into the main transcript.
#expect(!topNotes.contains { $0.contains("src/a.swift") && $0.hasPrefix("Lock acquired") })
#expect(!topNotes.contains { $0.hasPrefix("nvrsion:") })
// The untagged release stays top level, not pulled into the subagent's card.
#expect(topNotes.contains { $0.hasPrefix("Lock released on src/a.swift") })
#expect(!cardNotes.contains { $0.hasPrefix("Lock released") })
}
/// Note texts directly in `items` (not descending into subagent children).
private static func noteTexts(_ items: [TranscriptItem]) -> [String] {
items.compactMap { if case .note(let text, _, _, _) = $0.kind { return text } else { return nil } }
}
/// The `children` of the subagent spawn whose tool-call id is `cardID`, wherever it renders
/// (a lone `.tool` card or inside a coalesced `.toolBlock`).
private static func subagentChildren(_ items: [TranscriptItem], cardID: String) -> [TranscriptItem] {
for item in items {
switch item.kind {
case .tool(let g) where g.toolCallID == cardID: return g.children
case .toolBlock(let groups):
if let g = groups.first(where: { $0.toolCallID == cardID }) { return g.children }
default: break
}
}
return []
}
/// The hard case the registry exists for: a lock's `released` note lands when the file lands
/// in the parent several turns after the edit card it must fold onto is sealed.
@Test func lockReleaseManyTurnsAfterEdit() {