Display Subagent Events
Nucleic-Session: C0D404BC-7C32-4ECC-9F1E-3426BFE53298 Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user