fix(web): address skill row review feedback

This commit is contained in:
Yichen Jiang
2026-08-06 17:21:13 +08:00
parent 690f7ae035
commit a667d2cd64
39 files changed
+326 -142

No files matched your search

@@ -362,7 +362,8 @@ export function projectConversationHistory(
let contextGeneration = 0
for (const [index, event] of events.entries()) {
const view = entries[index]?.view
const entry = entries[index]
const view = entry?.view
if (event.type === 'tool/call') {
callIndex.set(String(event.data.callId), {
name: event.data.name,
@@ -370,8 +371,17 @@ export function projectConversationHistory(
time: event.time,
callView: view?.for === 'call' ? view.view : null,
})
} else if (event.type === 'tool/result' && view?.for === 'result') {
resultViews.set(event.seq, view.view)
} else if (event.type === 'tool/result') {
const callId = String(event.data.message.source.callId)
if (!callIndex.has(callId) && entry?.call !== undefined) {
callIndex.set(callId, {
name: entry.call.name,
argsRaw: entry.call.arguments,
time: entry.call.time,
callView: null,
})
}
if (view?.for === 'result') resultViews.set(event.seq, view.view)
}
if (isSurfaceEvent(event) && event.surfaceOp !== 'append') {
contextGeneration++
@@ -155,16 +155,16 @@ export interface TurnErrorNode {
code?: string
}
/** A tool result paired (when in-window) with its call head. */
/** A tool result paired with its durable call head when the Host can resolve it. */
export interface ToolResultNode {
kind: 'tool-result'
seq: number
/** Unix epoch ms from the tool/result session event. */
time: number
callId: string
/** Call head backfilled from the in-window tool/call; null when window truncation left the call outside (card head shows callId). */
/** Call head from the window or history envelope; null only when the durable log has no pair (card head shows callId). */
call: { name: string; argsRaw: string } | null
/** Unix epoch ms of the paired tool/call when the call is still in-window; used for call-row duration. */
/** Unix epoch ms of the paired tool/call; null when the durable log has no pair. */
callTime: number | null
content: readonly ContentBlock[]
isError: boolean
@@ -5,7 +5,7 @@ import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
import type { LlmRetryEventData } from '@deepseek-ai/dsh-llm-retry/types'
import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
import type {
HistoryEntry, IApiClient, MessageId, MuxFrame, QueueAction, RpcError,
HistoryEntry, HistoryToolCall, IApiClient, MessageId, MuxFrame, QueueAction, RpcError,
RpcId, RpcResponse, RpcResult, SessionId, SubagentAddress, ToolEventView,
} from '@deepseek-ai/dsh-client-connection/client'
// Value import from the inline-safe wire layer (not the connection plugin):
@@ -85,6 +85,8 @@ export class Session implements SessionFace {
/** Wire views aligned with `events` by index (envelope-level annotations; undefined = no view).
* Kept parallel rather than merged so `events` stays the raw log slice (model-visible ⟺ logged). */
private views: (ToolEventView | undefined)[] = []
/** Host-carried call metadata aligned with result entries when the call event is outside the page. */
private historyCalls: (HistoryToolCall | undefined)[] = []
private baseSeq = 0
private hasMore = false
private openState: OpenState = 'cold'
@@ -381,10 +383,11 @@ export class Session implements SessionFace {
}
this.events = [...older.map(e => e.event), ...this.events]
this.views = [...older.map(e => e.view), ...this.views]
this.historyCalls = [...older.map(e => e.call), ...this.historyCalls]
/* v8 ignore next -- the ?? arm needs older[0] undefined, but the empty-page branch above already returned. */
this.baseSeq = older[0]?.event.seq ?? this.baseSeq
this.hasMore = result.value.hasMore
this.transcript.reset(this.events, this.views) // prepend forces a rebuild (the window grew at the head)
this.transcript.reset(this.events, this.views, this.historyCalls) // prepend forces a rebuild (the window grew at the head)
this.rebuildDerivedFromWindow()
} catch (error) {
console.error('[web-runtime] loadOlder failed:', error)
@@ -411,6 +414,7 @@ export class Session implements SessionFace {
this.openError = null
this.events = []
this.views = []
this.historyCalls = []
this.baseSeq = 0
// Superseded, not settled: the baseline replay re-sends still-pending requested frames verbatim
// (same rpcId), re-minting fresh waits; a stale reference's respond() still reaches the host.
@@ -644,9 +648,10 @@ export class Session implements SessionFace {
private installWindow(entries: HistoryEntry[], hasMore: boolean, projections?: ProjectionsBaseline): void {
this.events = entries.map(e => e.event)
this.views = entries.map(e => e.view)
this.historyCalls = entries.map(e => e.call)
this.baseSeq = this.events[0]?.seq ?? 0
this.hasMore = hasMore
this.transcript.reset(this.events, this.views)
this.transcript.reset(this.events, this.views, this.historyCalls)
this.rebuildDerivedFromWindow()
if (projections !== undefined) this.projections.seed(projections)
const buffered = this.liveBuffer
@@ -661,6 +666,7 @@ export class Session implements SessionFace {
if (tailSeq !== null && event.seq <= tailSeq) return // replay overlap, drop
this.events.push(event)
this.views.push(view)
this.historyCalls.push(undefined)
this.transcript.append(event, view)
this.handoffPendingSteering(event)
this.applyEventSideEffects(event, view)
@@ -19,7 +19,9 @@ import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
// `sessions: ISessions` (TS2717, the one-program-per-side rule in
// docs/development.md).
import type { COMPACT_CHECKPOINT_SOURCE } from '@deepseek-ai/dsh-compact/checkpoint'
import type { ToolCallView, ToolEventView, ToolResultView } from '@deepseek-ai/dsh-client-connection/client'
import type {
HistoryToolCall, ToolCallView, ToolEventView, ToolResultView,
} from '@deepseek-ai/dsh-client-connection/client'
import type { CommandNode, CompactionSummaryNode, ConversationNode } from './conversation.ts'
import { toAssistantBlocks } from './conversation.ts'
import { contextForm, contextProvenance } from './context-provenance.ts'
@@ -213,8 +215,13 @@ export class TranscriptAdapter {
* and re-project the transcript.
* @param events - the new window contents (seq-ascending).
* @param views - per-event wire views aligned with `events` by index (undefined slots for view-less events).
* @param calls - host-carried result pairs aligned with `events` by index.
*/
reset(events: readonly SessionEvent[], views?: readonly (ToolEventView | undefined)[]): void {
reset(
events: readonly SessionEvent[],
views?: readonly (ToolEventView | undefined)[],
calls?: readonly (HistoryToolCall | undefined)[],
): void {
this.rev++
this.eventIndex = new Map()
this.callIdx = new Map()
@@ -228,7 +235,7 @@ export class TranscriptAdapter {
/* v8 ignore next -- dense-array guard: i stays within events.length, so the undefined arm needs a sparse array no caller builds. */
if (event === undefined) continue
this.eventIndex.set(event.seq, event)
this.indexCall(event, views?.[i])
this.indexCall(event, views?.[i], calls?.[i])
this.indexCommand(event)
if (this.steeringHistory.apply(event)) steeringSeqs.add(event.seq)
indexAssistantStepTiming(this.stepTimings, event)
@@ -338,9 +345,20 @@ export class TranscriptAdapter {
return true
}
private indexCall(event: SessionEvent, view?: ToolEventView): void {
private indexCall(event: SessionEvent, view?: ToolEventView, pairedCall?: HistoryToolCall): void {
if (event.type === 'tool/result') {
if (view?.for === 'result') this.resultViews.set(event.seq, view.view)
const callId = String(event.data.message.source.callId)
if (!this.callIdx.has(callId) && pairedCall !== undefined) {
this.callIdx.set(callId, {
name: pairedCall.name,
argsRaw: pairedCall.arguments,
turn: event.data.turn,
step: event.data.step,
time: pairedCall.time,
callView: null,
})
}
return
}
if (event.type !== 'tool/call') return