|
|
@@ -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>
|
|
|
</>
|
|
|
)}
|