Merge nucleic/vivid-spruce-robin-bacs into dev
This commit is contained in:
@@ -144,9 +144,9 @@ struct SettingsView: View {
|
|||||||
} header: {
|
} header: {
|
||||||
Text("Transcript")
|
Text("Transcript")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Advanced detail surfaces per-turn token counts, rate-limit notices, turn "
|
Text("Advanced detail surfaces model thought traces, per-turn token counts, "
|
||||||
+ "markers, and raw events. Hidden by default so consecutive tool calls "
|
+ "rate-limit notices, turn markers, and raw events. Hidden by default "
|
||||||
+ "read as one group.")
|
+ "so conversations stay focused and consecutive tool calls read as one group.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import NucleicProtocol
|
|||||||
/// projector resets and re-folds from scratch, so correctness never rests on them.
|
/// projector resets and re-folds from scratch, so correctness never rests on them.
|
||||||
/// 3. **No tool run is split.** `coalesceToolRuns` merges adjacent `.tool` items, so the seam
|
/// 3. **No tool run is split.** `coalesceToolRuns` merges adjacent `.tool` items, so the seam
|
||||||
/// only falls where the last folded item is a hard separator — a visible non-tool row that a
|
/// only falls where the last folded item is a hard separator — a visible non-tool row that a
|
||||||
/// future tool call can't merge across. (Empty redacted-thinking rows are transparent to runs
|
/// future tool call can't merge across. (Thought traces and other advanced-detail rows are
|
||||||
/// and therefore to this rule too.)
|
/// transparent to runs and therefore to this rule too.)
|
||||||
/// 4. **Lock notes fold exactly, even across the seam.** A lock's `released` note lands when the
|
/// 4. **Lock notes fold exactly, even across the seam.** A lock's `released` note lands when the
|
||||||
/// file lands in the parent — potentially many turns after the edit it brackets — so sealed
|
/// file lands in the parent — potentially many turns after the edit it brackets — so sealed
|
||||||
/// edit cards stay reachable through a registry (`TranscriptProjection.PriorEdit`): a tail
|
/// edit cards stay reachable through a registry (`TranscriptProjection.PriorEdit`): a tail
|
||||||
@@ -205,11 +205,8 @@ final class IncrementalTranscriptProjection {
|
|||||||
case separator
|
case separator
|
||||||
/// A `.tool` row a future adjacent call could merge with.
|
/// A `.tool` row a future adjacent call could merge with.
|
||||||
case tool
|
case tool
|
||||||
/// A thinking row: a separator iff its final text is non-empty (an empty redacted block
|
/// A thought trace or other row held aside from the conversation, or a lock note that
|
||||||
/// is transparent to run coalescing, so it must be transparent to the seam rule too).
|
/// may fold away. It counts as nothing so the seam waits for the next hard separator.
|
||||||
case thinking(String)
|
|
||||||
/// A lock note that may fold away (dropping it can fuse the runs around it), so it
|
|
||||||
/// counts as nothing — the seam just waits for the next hard separator.
|
|
||||||
case transparent
|
case transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,9 +229,7 @@ final class IncrementalTranscriptProjection {
|
|||||||
|
|
||||||
var spans: [String: IDSpan] = [:]
|
var spans: [String: IDSpan] = [:]
|
||||||
var creations: [Creation?] = Array(repeating: nil, count: n)
|
var creations: [Creation?] = Array(repeating: nil, count: n)
|
||||||
var thinkingText: [String: String] = [:]
|
|
||||||
var seenMessageItem = Set<String>()
|
var seenMessageItem = Set<String>()
|
||||||
var seenThinkingItem = Set<String>()
|
|
||||||
var seenToolItem = Set<String>()
|
var seenToolItem = Set<String>()
|
||||||
var lastChunkInScope: [String: String] = [:]
|
var lastChunkInScope: [String: String] = [:]
|
||||||
var lastBoundary = -1 // region index of the latest turnCompleted/runFinished
|
var lastBoundary = -1 // region index of the latest turnCompleted/runFinished
|
||||||
@@ -256,9 +251,7 @@ final class IncrementalTranscriptProjection {
|
|||||||
if seenMessageItem.insert(c.messageID).inserted { creations[r] = .separator }
|
if seenMessageItem.insert(c.messageID).inserted { creations[r] = .separator }
|
||||||
case .thinking(let c):
|
case .thinking(let c):
|
||||||
trackChunk(c, in: &spans, lastChunkInScope: &lastChunkInScope)
|
trackChunk(c, in: &spans, lastChunkInScope: &lastChunkInScope)
|
||||||
let existing = thinkingText[c.messageID] ?? ""
|
creations[r] = .transparent
|
||||||
thinkingText[c.messageID] = c.isPartial ? existing + c.text : c.text
|
|
||||||
if seenThinkingItem.insert(c.messageID).inserted { creations[r] = .thinking(c.messageID) }
|
|
||||||
case .toolCallStarted(let c), .toolCallCompleted(let c):
|
case .toolCallStarted(let c), .toolCallCompleted(let c):
|
||||||
if seenToolItem.insert(c.toolCallID).inserted { creations[r] = .tool }
|
if seenToolItem.insert(c.toolCallID).inserted { creations[r] = .tool }
|
||||||
case .toolResult(let result):
|
case .toolResult(let result):
|
||||||
@@ -310,20 +303,13 @@ final class IncrementalTranscriptProjection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run-split rule (3): replay creations in item order; a seam is placeable after event r
|
// Run-split rule (3): replay creations in item order; a seam is placeable after event r
|
||||||
// only while the last solid (visible, surviving) item is a hard separator. The replay
|
// only while the last solid (visible, surviving) item is a hard separator.
|
||||||
// resolves each thinking row against its *final* region text, which is exactly what the
|
|
||||||
// sealed fold will contain (open ids were already excluded by `cap`).
|
|
||||||
var separatorOK = [Bool](repeating: false, count: n)
|
var separatorOK = [Bool](repeating: false, count: n)
|
||||||
var lastSolidIsSeparator = true // sealed prefix is empty or ends with a separator (invariant)
|
var lastSolidIsSeparator = true // sealed prefix is empty or ends with a separator (invariant)
|
||||||
for r in 0..<n {
|
for r in 0..<n {
|
||||||
switch creations[r] {
|
switch creations[r] {
|
||||||
case .separator: lastSolidIsSeparator = true
|
case .separator: lastSolidIsSeparator = true
|
||||||
case .tool: lastSolidIsSeparator = false
|
case .tool: lastSolidIsSeparator = false
|
||||||
case .thinking(let id):
|
|
||||||
let text = thinkingText[id] ?? ""
|
|
||||||
if !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
||||||
lastSolidIsSeparator = true
|
|
||||||
}
|
|
||||||
case .transparent, nil: break
|
case .transparent, nil: break
|
||||||
}
|
}
|
||||||
separatorOK[r] = lastSolidIsSeparator
|
separatorOK[r] = lastSolidIsSeparator
|
||||||
|
|||||||
@@ -64,15 +64,14 @@ struct TranscriptItem: Identifiable, Equatable {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Diagnostic, debug-level rows: per-turn token tallies, rate-limit notices, turn markers, and
|
/// Advanced-detail rows: model thought traces, per-turn token tallies, rate-limit notices,
|
||||||
/// unrecognized raw passthrough events. Hidden by default to keep the transcript clean — they're
|
/// turn markers, and unrecognized raw passthrough events. Hidden by default to keep the
|
||||||
/// the "token counts and other lines between tool calls" that fragmented a run of cards — and
|
/// transcript focused on the conversation, and surfaced only when "advanced detail"
|
||||||
/// surfaced only when "advanced detail" (`showRaw`) is on. Prose, thinking, tool calls,
|
/// (`showRaw`) is on. Prose, tool calls, session-start, approvals, run outcome, and errors
|
||||||
/// session-start, approvals, run outcome, and errors are never debug. Mirrors the Mac's
|
/// are never debug. Mirrors the Mac's `TranscriptItem.isDebug`.
|
||||||
/// `TranscriptItem.isDebug`.
|
|
||||||
var isDebug: Bool {
|
var isDebug: Bool {
|
||||||
switch kind {
|
switch kind {
|
||||||
case .usage, .rateLimit, .turnBoundary, .raw: return true
|
case .thinking, .usage, .rateLimit, .turnBoundary, .raw: return true
|
||||||
default: return false
|
default: return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,8 +415,9 @@ enum TranscriptProjection {
|
|||||||
showRaw: Bool, showLockEvents: Bool
|
showRaw: Bool, showLockEvents: Bool
|
||||||
) -> [TranscriptItem] {
|
) -> [TranscriptItem] {
|
||||||
let items = coalesceToolRuns(flatItems(events, showRaw: showRaw, showLockEvents: showLockEvents))
|
let items = coalesceToolRuns(flatItems(events, showRaw: showRaw, showLockEvents: showLockEvents))
|
||||||
guard depth < maxSubagentDepth else { return items }
|
let visible = showRaw ? items : items.filter { !$0.isDebug }
|
||||||
return items.map {
|
guard depth < maxSubagentDepth else { return visible }
|
||||||
|
return visible.map {
|
||||||
attachSubagentChildren($0, byParent: byParent, depth: depth,
|
attachSubagentChildren($0, byParent: byParent, depth: depth,
|
||||||
showRaw: showRaw, showLockEvents: showLockEvents)
|
showRaw: showRaw, showLockEvents: showLockEvents)
|
||||||
}
|
}
|
||||||
@@ -462,8 +462,9 @@ enum TranscriptProjection {
|
|||||||
/// followed by the agent's own `Bash` calls yields a fan-out card *and* a tool block, not one
|
/// followed by the agent's own `Bash` calls yields a fan-out card *and* a tool block, not one
|
||||||
/// generic block titled by the first `Task` that would swallow the fan-out.
|
/// generic block titled by the first `Task` that would swallow the fan-out.
|
||||||
///
|
///
|
||||||
/// `flatItems` already drops the only kind the phone has (empty redacted thinking), so the
|
/// Advanced-detail rows, including non-empty thought traces, take the hold path in the live
|
||||||
/// hold path is a guard for a caller passing unfiltered items — not a live case.
|
/// projection. They are re-emitted after the block when detail is enabled and filtered out
|
||||||
|
/// otherwise.
|
||||||
static func coalesceToolRuns(_ flat: [TranscriptItem]) -> [TranscriptItem] {
|
static func coalesceToolRuns(_ flat: [TranscriptItem]) -> [TranscriptItem] {
|
||||||
var out: [TranscriptItem] = []
|
var out: [TranscriptItem] = []
|
||||||
var run: [TranscriptItem] = [] // consecutive `.tool` items
|
var run: [TranscriptItem] = [] // consecutive `.tool` items
|
||||||
|
|||||||
Reference in New Issue
Block a user