round 2: address manual compaction review findings
This commit is contained in:
@@ -27,7 +27,8 @@ const KIND_LABEL: Record<TrajectoryCellKind, string> = {
|
||||
}
|
||||
|
||||
interface TableRecord {
|
||||
turn: number
|
||||
turn: number | null
|
||||
section: number
|
||||
group: string
|
||||
groupStart: boolean
|
||||
turnStart: boolean
|
||||
@@ -69,7 +70,7 @@ interface ToolCallTextParts {
|
||||
}
|
||||
|
||||
interface SelectedRequest {
|
||||
turn: number
|
||||
turn: number | null
|
||||
number: number
|
||||
group: string
|
||||
}
|
||||
@@ -237,15 +238,12 @@ export interface TrajectoryTableProps {
|
||||
onToggleAssistant: (index: number) => void
|
||||
}
|
||||
|
||||
/** One request identity paired with its session-global number. */
|
||||
export interface TrajectoryRequestNumber {
|
||||
/** Request-inspector fields shared by ordinary generation and compaction. */
|
||||
interface TrajectoryRequestNumberBase {
|
||||
/** Request anchor event sequence; absent for the currently streaming ordinary request. */
|
||||
seq?: number
|
||||
turn: number
|
||||
step: number
|
||||
group: string
|
||||
number: number
|
||||
purpose?: 'compaction'
|
||||
status?: 'complete' | 'running' | 'error'
|
||||
startedAt?: number
|
||||
completedAt?: number | null
|
||||
@@ -261,6 +259,20 @@ export interface TrajectoryRequestNumber {
|
||||
cumulativeUsage?: TrajectoryUsage
|
||||
}
|
||||
|
||||
/** One purpose-discriminated request identity paired with its session-global number. */
|
||||
export type TrajectoryRequestNumber = TrajectoryRequestNumberBase & (
|
||||
| {
|
||||
purpose?: 'assistant'
|
||||
turn: number
|
||||
step: number
|
||||
}
|
||||
| {
|
||||
purpose: 'compaction'
|
||||
turn: number | null
|
||||
step: 0
|
||||
}
|
||||
)
|
||||
|
||||
/** Disjoint provider token buckets for one request or a session prefix. */
|
||||
export interface TrajectoryUsage {
|
||||
input?: number
|
||||
@@ -271,17 +283,18 @@ export interface TrajectoryUsage {
|
||||
}
|
||||
|
||||
function flattenRecords(turns: readonly TrajectoryTurnModel[]): TableRecord[] {
|
||||
return turns.flatMap((turn) => {
|
||||
let firstInTurn = true
|
||||
return turns.flatMap((turn, section) => {
|
||||
let firstInSection = true
|
||||
const records = turn.groups.flatMap((group) => {
|
||||
return group.cells.map((cell, index) => {
|
||||
const turnStart = firstInTurn
|
||||
const turnStart = firstInSection
|
||||
&& cell.requestOnly !== true
|
||||
&& cell.kind !== 'system'
|
||||
&& cell.kind !== 'compacted'
|
||||
if (turnStart) firstInTurn = false
|
||||
&& (cell.kind !== 'compacted' || turn.turn === null)
|
||||
if (turnStart) firstInSection = false
|
||||
return {
|
||||
turn: turn.turn,
|
||||
section,
|
||||
group: group.title,
|
||||
groupStart: index === 0,
|
||||
turnStart,
|
||||
@@ -305,18 +318,18 @@ function filterRecords(
|
||||
record.cell.requestOnly !== true && matches.has(record.cell.index),
|
||||
)
|
||||
.map(record => ({ ...record, groupStart: false, turnStart: false, turnEnd: false }))
|
||||
const startedTurns = new Set<number>()
|
||||
const startedSections = new Set<number>()
|
||||
for (const [index, record] of filtered.entries()) {
|
||||
const previous = filtered[index - 1]
|
||||
const next = filtered[index + 1]
|
||||
record.groupStart = previous === undefined
|
||||
|| previous.turn !== record.turn
|
||||
|| previous.section !== record.section
|
||||
|| previous.group !== record.group
|
||||
record.turnStart = !startedTurns.has(record.turn)
|
||||
record.turnStart = !startedSections.has(record.section)
|
||||
&& record.cell.kind !== 'system'
|
||||
&& record.cell.kind !== 'compacted'
|
||||
if (record.turnStart) startedTurns.add(record.turn)
|
||||
record.turnEnd = next === undefined || next.turn !== record.turn
|
||||
&& (record.cell.kind !== 'compacted' || record.turn === null)
|
||||
if (record.turnStart) startedSections.add(record.section)
|
||||
record.turnEnd = next === undefined || next.section !== record.section
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
@@ -327,10 +340,14 @@ function requestStep(group: string): number | undefined {
|
||||
return Number.isInteger(value) && value > 0 ? value : undefined
|
||||
}
|
||||
|
||||
function requestKey(turn: number, group: string): string {
|
||||
function requestKey(turn: number | null, group: string): string {
|
||||
return `${turn}\u0000${group}`
|
||||
}
|
||||
|
||||
function sectionLabel(turn: number | null): string {
|
||||
return turn === null ? 'Between turns' : `Turn ${turn}`
|
||||
}
|
||||
|
||||
function indexRequestNumbers(
|
||||
records: readonly TableRecord[],
|
||||
sessionNumbers: readonly TrajectoryRequestNumber[] | undefined,
|
||||
@@ -372,12 +389,13 @@ function collapseTurnRecords(
|
||||
if (collapsedTurns.size === 0) return [...records]
|
||||
const recordsByTurn = new Map<number, TableRecord[]>()
|
||||
for (const record of records) {
|
||||
if (record.turn === null) continue
|
||||
const turnRecords = recordsByTurn.get(record.turn) ?? []
|
||||
turnRecords.push(record)
|
||||
recordsByTurn.set(record.turn, turnRecords)
|
||||
}
|
||||
return records.flatMap((record) => {
|
||||
if (!collapsedTurns.has(record.turn)) return [record]
|
||||
if (record.turn === null || !collapsedTurns.has(record.turn)) return [record]
|
||||
const turnRecords = recordsByTurn.get(record.turn) ?? [record]
|
||||
if (record.cell.requestOnly === true || record.cell.kind === 'system') return [record]
|
||||
const contentRecords = turnRecords.filter(candidate =>
|
||||
@@ -1486,7 +1504,7 @@ export function TrajectoryTable({
|
||||
const selectedRequestCumulativeUsage =
|
||||
selectedRequestInfo?.cumulativeUsage ?? selectedRequestUsage
|
||||
const selectedRequestOptions = selectedRequestInfo?.requestConfig
|
||||
const activeTurn = selectedRequest?.turn ?? selected?.turn
|
||||
const activeTurn = selectedRequest === null ? selected?.turn : selectedRequest.turn
|
||||
const selectedTabs = selectedRequest !== null
|
||||
? REQUEST_TABS.filter(tab => tab.id !== 'options' || selectedRequestOptions !== undefined)
|
||||
: selected === undefined ? [] : detailTabs(selected)
|
||||
@@ -1554,7 +1572,7 @@ export function TrajectoryTable({
|
||||
|
||||
const openRecordSummary = (target: TableRecord) => {
|
||||
const targetAt = allRecords.findIndex(record => record.cell.index === target.cell.index)
|
||||
if (collapsedTurns.has(target.turn)) onToggleTurn(target.turn)
|
||||
if (target.turn !== null && collapsedTurns.has(target.turn)) onToggleTurn(target.turn)
|
||||
if (target.cell.kind === 'tool' || target.cell.kind === 'subtool') {
|
||||
for (let i = targetAt - 1; i >= 0; i--) {
|
||||
const candidate = allRecords[i]
|
||||
@@ -1600,7 +1618,7 @@ export function TrajectoryTable({
|
||||
&& record.cell.index === allRecords[0]?.cell.index
|
||||
const request = record.groupStart
|
||||
&& !isCollapsedSummary
|
||||
&& !collapsedTurns.has(record.turn)
|
||||
&& (record.turn === null || !collapsedTurns.has(record.turn))
|
||||
? requestNumbers.get(requestKey(record.turn, record.group))
|
||||
: undefined
|
||||
const requestInfo = request === undefined
|
||||
@@ -1641,13 +1659,14 @@ export function TrajectoryTable({
|
||||
? undefined
|
||||
: isCollapsedSummary
|
||||
? () => {
|
||||
if (record.collapsedSummaryKind === 'turn') onToggleTurn(record.turn)
|
||||
else onToggleAssistant(record.cell.index)
|
||||
if (record.collapsedSummaryKind === 'turn' && record.turn !== null) {
|
||||
onToggleTurn(record.turn)
|
||||
} else onToggleAssistant(record.cell.index)
|
||||
}
|
||||
: () => { selectRecord(record.cell.index) }}
|
||||
onDoubleClick={(event) => {
|
||||
if (isCollapsedSummary || isRequestOnly) return
|
||||
if (collapsedTurns.has(record.turn)) {
|
||||
if (record.turn !== null && collapsedTurns.has(record.turn)) {
|
||||
event.preventDefault()
|
||||
onToggleTurn(record.turn)
|
||||
return
|
||||
@@ -1661,6 +1680,7 @@ export function TrajectoryTable({
|
||||
return
|
||||
}
|
||||
if (!record.turnStart) return
|
||||
if (record.turn === null) return
|
||||
if (allRecords.filter(candidate =>
|
||||
candidate.turn === record.turn
|
||||
&& candidate.cell.requestOnly !== true
|
||||
@@ -1673,8 +1693,9 @@ export function TrajectoryTable({
|
||||
if (event.key !== 'Enter' && event.key !== ' ') return
|
||||
event.preventDefault()
|
||||
if (isCollapsedSummary) {
|
||||
if (record.collapsedSummaryKind === 'turn') onToggleTurn(record.turn)
|
||||
else onToggleAssistant(record.cell.index)
|
||||
if (record.collapsedSummaryKind === 'turn' && record.turn !== null) {
|
||||
onToggleTurn(record.turn)
|
||||
} else onToggleAssistant(record.cell.index)
|
||||
return
|
||||
}
|
||||
selectRecord(record.cell.index)
|
||||
@@ -1701,7 +1722,9 @@ export function TrajectoryTable({
|
||||
onDoubleClick={(event) => { event.stopPropagation() }}
|
||||
/>
|
||||
)}
|
||||
{activeTurn === record.turn && !isInitialSystem && (
|
||||
{record.turn !== null
|
||||
&& activeTurn === record.turn
|
||||
&& !isInitialSystem && (
|
||||
<span className={css.turnRail} aria-hidden="true" />
|
||||
)}
|
||||
{!isCollapsedSummary && selectedIndex === record.cell.index && (
|
||||
@@ -1715,7 +1738,7 @@ export function TrajectoryTable({
|
||||
? `${css.turnLabel} ${css.turnLabelActive}`
|
||||
: css.turnLabel}
|
||||
>
|
||||
Turn {record.turn}
|
||||
{sectionLabel(record.turn)}
|
||||
</span>
|
||||
)}
|
||||
<div className={css.eventInner}>
|
||||
@@ -1895,8 +1918,8 @@ export function TrajectoryTable({
|
||||
</span>
|
||||
<span className={css.detailsLocation}>
|
||||
{selectedRequestInfo?.purpose === 'compaction'
|
||||
? `Compaction · Turn ${selectedRequest.turn}`
|
||||
: `Turn ${selectedRequest.turn}`}
|
||||
? `Compaction · ${sectionLabel(selectedRequest.turn)}`
|
||||
: sectionLabel(selectedRequest.turn)}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
@@ -1927,8 +1950,8 @@ export function TrajectoryTable({
|
||||
</span>
|
||||
<span className={css.detailsLocation}>
|
||||
{selected.cell.kind === 'compacted'
|
||||
? `Turn ${selected.turn}`
|
||||
: `Turn ${selected.turn} · ${selected.group}`}
|
||||
? sectionLabel(selected.turn)
|
||||
: `${sectionLabel(selected.turn)} · ${selected.group}`}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -315,9 +315,9 @@ export const TrajectoryTimeline = memo(function TrajectoryTimeline({
|
||||
)}
|
||||
<div className={css.turnBoundaries} aria-hidden="true">
|
||||
{model.turnBoundaries
|
||||
.slice(1)
|
||||
.filter(boundary =>
|
||||
boundary.time >= domainStart
|
||||
boundary.time > model.start
|
||||
&& boundary.time >= domainStart
|
||||
&& boundary.time <= domainStart + domainDuration)
|
||||
.map(boundary => (
|
||||
<span
|
||||
|
||||
@@ -102,7 +102,7 @@ function searchMatches(
|
||||
...(cell.outputBlocks ?? []),
|
||||
]
|
||||
const text = [
|
||||
`turn ${turn.turn}`,
|
||||
turn.turn === null ? 'between turns' : `turn ${turn.turn}`,
|
||||
group.title,
|
||||
cell.kind,
|
||||
cell.kind === 'message' ? 'assistant' : undefined,
|
||||
@@ -380,13 +380,15 @@ export function TrajectoryView({
|
||||
const collapsibleTurnIds = useMemo(
|
||||
() => turns
|
||||
.filter(turn =>
|
||||
turn.turn !== null
|
||||
&&
|
||||
turn.groups.reduce(
|
||||
(count, group) =>
|
||||
count + group.cells.filter(cell =>
|
||||
cell.requestOnly !== true && cell.kind !== 'system').length,
|
||||
0,
|
||||
) > 1)
|
||||
.map(turn => turn.turn),
|
||||
.flatMap(turn => turn.turn === null ? [] : [turn.turn]),
|
||||
[turns],
|
||||
)
|
||||
const allTurnsCollapsed = collapsibleTurnIds.length > 0
|
||||
|
||||
@@ -107,6 +107,8 @@ export function trajectoryBranchContainsRequest(
|
||||
request.resultSeq !== undefined
|
||||
&& branch.retainedSurfaceSeqs.has(request.resultSeq)
|
||||
) || (
|
||||
request.purpose === 'compaction'
|
||||
&&
|
||||
request.replacementSeq !== undefined
|
||||
&& branch.retainedSurfaceSeqs.has(request.replacementSeq)
|
||||
)
|
||||
|
||||
@@ -24,9 +24,9 @@ export interface TrajectoryGroupModel {
|
||||
cells: readonly TrajectoryCellProps[]
|
||||
}
|
||||
|
||||
/** One sticky-turn section. */
|
||||
/** One sticky turn, or a standalone compaction section between turns. */
|
||||
export interface TrajectoryTurnModel {
|
||||
turn: number
|
||||
turn: number | null
|
||||
groups: readonly TrajectoryGroupModel[]
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ interface TurnBucket {
|
||||
groups: LaidGroup[]
|
||||
}
|
||||
|
||||
type AssistantRequestView = Extract<RequestView, { purpose: 'assistant' }>
|
||||
type CompactionRequestView = Extract<RequestView, { purpose: 'compaction' }>
|
||||
|
||||
type InputNode = Extract<
|
||||
ConversationSnapshot['nodes'][number],
|
||||
{ kind: 'user' | 'steering' | 'context' }
|
||||
@@ -81,18 +84,18 @@ type OrderedLayoutEntry =
|
||||
| {
|
||||
kind: 'compaction'
|
||||
seq: number
|
||||
request: RequestView
|
||||
request: CompactionRequestView
|
||||
}
|
||||
| {
|
||||
kind: 'system'
|
||||
seq: number
|
||||
request: RequestView
|
||||
request: AssistantRequestView
|
||||
change: RequestPromptChange
|
||||
}
|
||||
| {
|
||||
kind: 'request'
|
||||
seq: number
|
||||
request: RequestView
|
||||
request: AssistantRequestView
|
||||
}
|
||||
|
||||
function layoutEntryOrder(entry: OrderedLayoutEntry): number {
|
||||
@@ -136,6 +139,7 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T
|
||||
if (startedAt !== null) callStartById.set(call.callId, startedAt)
|
||||
}
|
||||
const turns = new Map<number, TurnBucket>()
|
||||
const standaloneCompactions: TurnBucket[] = []
|
||||
let index = 0
|
||||
let prevAbsTime: number | null = null
|
||||
let lastAssistantTurn: number | null = null
|
||||
@@ -191,13 +195,16 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T
|
||||
nodeIndex,
|
||||
})),
|
||||
...requests
|
||||
.filter(request => request.purpose === 'compaction')
|
||||
.filter((request): request is CompactionRequestView =>
|
||||
request.purpose === 'compaction')
|
||||
.map(request => ({
|
||||
kind: 'compaction' as const,
|
||||
seq: request.startSeq,
|
||||
request,
|
||||
})),
|
||||
...requests.flatMap(request => request.promptChange === undefined || request.prompt === undefined
|
||||
...requests.flatMap(request => request.purpose !== 'assistant'
|
||||
|| request.promptChange === undefined
|
||||
|| request.prompt === undefined
|
||||
? []
|
||||
: [{
|
||||
kind: 'system' as const,
|
||||
@@ -206,7 +213,8 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T
|
||||
change: request.promptChange,
|
||||
}]),
|
||||
...requests
|
||||
.filter(request => request.purpose === 'assistant')
|
||||
.filter((request): request is AssistantRequestView =>
|
||||
request.purpose === 'assistant')
|
||||
.filter(request =>
|
||||
!representedRequests.has(`${request.turn}\u0000${request.step}`),
|
||||
)
|
||||
@@ -297,13 +305,17 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T
|
||||
startedAt: finiteTime(request.startedAt),
|
||||
}
|
||||
attachUsage(cell, request.usage as UsageLike | undefined)
|
||||
bucket(request.turn).groups.push({
|
||||
title: `Compaction ${request.startSeq}`,
|
||||
laid: [{
|
||||
absTime: finiteTime(request.startedAt),
|
||||
cell,
|
||||
const compaction: TurnBucket = {
|
||||
groups: [{
|
||||
title: `Compaction ${request.startSeq}`,
|
||||
laid: [{
|
||||
absTime: finiteTime(request.startedAt),
|
||||
cell,
|
||||
}],
|
||||
}],
|
||||
})
|
||||
}
|
||||
if (request.turn === null) standaloneCompactions.push(compaction)
|
||||
else bucket(request.turn).groups.push(...compaction.groups)
|
||||
prevAbsTime = finiteTime(request.completedAt) ?? finiteTime(request.startedAt) ?? prevAbsTime
|
||||
continue
|
||||
}
|
||||
@@ -446,15 +458,16 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T
|
||||
turns.set(1, first)
|
||||
}
|
||||
|
||||
for (const entry of turns.values()) {
|
||||
for (const entry of [...turns.values(), ...standaloneCompactions]) {
|
||||
for (const group of entry.groups) {
|
||||
for (const laid of group.laid) attachToolSchema(laid, callSchemas)
|
||||
}
|
||||
}
|
||||
|
||||
return [...turns.entries()]
|
||||
.sort(([a], [b]) => a - b)
|
||||
.map(([turn, entry]) => toTurnModel(turn, entry))
|
||||
return [
|
||||
...[...turns.entries()].map(([turn, entry]) => toTurnModel(turn, entry)),
|
||||
...standaloneCompactions.map(entry => toTurnModel(null, entry)),
|
||||
].sort((left, right) => firstCellIndex(left) - firstCellIndex(right))
|
||||
}
|
||||
|
||||
function attachToolSchema(
|
||||
@@ -468,7 +481,7 @@ function attachToolSchema(
|
||||
}
|
||||
|
||||
function toTurnModel(
|
||||
turn: number,
|
||||
turn: number | null,
|
||||
entry: TurnBucket,
|
||||
): TrajectoryTurnModel {
|
||||
const groups = entry.groups.map(({ title, laid }): TrajectoryGroupModel => {
|
||||
@@ -482,6 +495,14 @@ function toTurnModel(
|
||||
return { turn, groups }
|
||||
}
|
||||
|
||||
/** Chronological section position from the fold's monotonically assigned cell indexes. */
|
||||
function firstCellIndex(turn: TrajectoryTurnModel): number {
|
||||
return Math.min(
|
||||
...turn.groups.flatMap(group => group.cells.map(cell => cell.index)),
|
||||
Number.POSITIVE_INFINITY,
|
||||
)
|
||||
}
|
||||
|
||||
/** Wall-span duration + tool histogram, e.g. `1.5 s bash×6`. */
|
||||
function groupDescription(laid: readonly LaidCell[]): string | undefined {
|
||||
const parts: string[] = []
|
||||
|
||||
@@ -86,10 +86,12 @@ export function deriveTrajectoryTimeline(
|
||||
group.cells.filter(cell => cell.requestOnly !== true),
|
||||
)
|
||||
if (cells.length === 0) continue
|
||||
turnBoundaries.push({
|
||||
turn: turn.turn,
|
||||
time: spans.length,
|
||||
})
|
||||
if (turn.turn !== null) {
|
||||
turnBoundaries.push({
|
||||
turn: turn.turn,
|
||||
time: spans.length,
|
||||
})
|
||||
}
|
||||
spans.push(...cells.map((cell, offset): TrajectoryTimelineSpan => ({
|
||||
start: spans.length + offset,
|
||||
end: spans.length + offset + 1,
|
||||
@@ -147,10 +149,12 @@ function deriveTimedTimeline(
|
||||
start: span.start - removedUserIdle,
|
||||
end: (actualDuration ? span.end : span.start) - removedUserIdle,
|
||||
})))
|
||||
turnBoundaries.push({
|
||||
turn: turn.turn,
|
||||
time: turnStart - removedUserIdle,
|
||||
})
|
||||
if (turn.turn !== null) {
|
||||
turnBoundaries.push({
|
||||
turn: turn.turn,
|
||||
time: turnStart - removedUserIdle,
|
||||
})
|
||||
}
|
||||
previousTurnEnd = previousTurnEnd === null
|
||||
? turnEnd
|
||||
: Math.max(previousTurnEnd, turnEnd)
|
||||
|
||||
Reference in New Issue
Block a user