feat(dsh): adapt to DSH 0.1.5-alpha.1
Move the plugin onto the DSH 0.1.5-alpha.1 baseline (peer floor ^0.1.5-alpha.1, CI pinned to the alpha dist-tag) and fix the two host contract changes that alpha line introduced. Baseline: - all @deepseek-ai/dsh-* peer/dev deps -> 0.1.5-alpha.1; optional peer @deepseek-ai/dsh-client-ui-sidebar-right (declared, unused until the native-surface phase); devDep @deepseek-ai/dsh-agent-loop (inbox fold) - dsh.plugin.json engines >=0.1.5-alpha.1; version 0.19.0-alpha.0; SIDEBAR_SERVICE_VERSION follows Live assistant stream (assistant/chunk is gone): - 0.1.5 publishes in-flight model deltas as process-local agent/assistant-stream frames instead of durable assistant/chunk events and embeds the settled stream in assistant/message / assistant/attempt. src/assistant-live.ts folds the frames into a bounded per-session buffer; sidechat.events serves the current attempt as `live` rows (replaced every poll, never a delta) and the transcript renders them; the inherited in-progress snapshot reads message content / attempt stream / live frames. Cold session reads (persistence.inspect is gone): - 0.1.5 reads a stored session through an explicit read handle (open(id,'read') -> handle.read() -> close()). src/session-store.ts owns that protocol; every cold read (sidechat transcript, cold-resume preset, session.cwd, changes replay) goes through it. Dead code and docs: - drop the remote.session.openWorkspacePath interception: 0.1.5's chat opens files through ctx.sidebarRight.openResource instead, so the wrapper had no caller and would hijack the host's "open in app" gesture; the interceptOpenPath pref, its settings row and its locale keys go with it - AGENTS.md §3 + external-plugin-guide.md describe the new baseline - vitest setup bridges Node's localStorage accessor to jsdom's store (the pre-existing 26 failures on Node >= 22) - desktop-layout e2e seeds through host RPC and types into the Lexical composer (0.1.5 replaced the textarea) instead of driving the directory picker Verified: pnpm typecheck / lint / build / test (124 files, 1327 tests) / check:consumer-types, plus pnpm test:mount against a real @deepseek-ai/dsh@0.1.5-alpha.1 host (18/18).
This commit is contained in:
@@ -58,6 +58,7 @@ import {
|
||||
sideThreadRows,
|
||||
threadHasCompletedTurn,
|
||||
threadTrailingPending,
|
||||
type SidechatLiveEvent,
|
||||
type SidechatThreadInfo,
|
||||
} from '../sidechat-core.ts'
|
||||
import {
|
||||
@@ -112,6 +113,8 @@ const inFlightStarts = new Set<string>()
|
||||
* the afterSeq delta and never re-download what they already hold). */
|
||||
interface ThreadCache {
|
||||
entries: SidebarHistoryEntry[]
|
||||
/** The CURRENT attempt's live deltas (replaced every pull, never merged). */
|
||||
live: SidechatLiveEvent[]
|
||||
}
|
||||
|
||||
/** Row-render labels (locale-dependent, memoized once per mount). */
|
||||
@@ -383,7 +386,7 @@ export function SideChatView(props: {
|
||||
const [info, setInfo] = useState<SidechatThreadInfo | null>(null)
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
|
||||
const cacheRef = useRef<ThreadCache>({ entries: [] })
|
||||
const cacheRef = useRef<ThreadCache>({ entries: [], live: [] })
|
||||
// The previous poll's rows (see the mapping's reuse pass below).
|
||||
const prevRowsRef = useRef<SidechatTranscriptRow[]>([])
|
||||
const controllerRef = useRef<AbortController | null>(null)
|
||||
@@ -457,14 +460,18 @@ export function SideChatView(props: {
|
||||
try {
|
||||
const cache = cacheRef.current
|
||||
const afterSeq = cache.entries.at(-1)?.event.seq
|
||||
const { events } = await api.sidechatEvents(childId, afterSeq, controller.signal)
|
||||
const { events, live } = await api.sidechatEvents(childId, afterSeq, controller.signal)
|
||||
// The live set is the CURRENT attempt, so it replaces whatever the
|
||||
// previous pull held; a settled step drops out of the host buffer and
|
||||
// its durable assistant/message takes over in the mapping.
|
||||
cache.live = live ?? []
|
||||
if (events.length > 0) {
|
||||
// Wire events arrive as parsed JSON; the mirror narrows data to the
|
||||
// record the mapping reads.
|
||||
const incoming = events.map(event => ({ event: event as SidebarSessionEvent }))
|
||||
cache.entries = mergeBySeq(cache.entries, incoming)
|
||||
setRevision(value => value + 1)
|
||||
}
|
||||
setRevision(value => value + 1)
|
||||
} catch {
|
||||
// Aborted by a newer pull or a wire failure: keep the last rows.
|
||||
}
|
||||
@@ -482,7 +489,7 @@ export function SideChatView(props: {
|
||||
// Reset the transcript cache whenever the binding changes, then focus
|
||||
// the composer — it owns the first message of a fresh thread.
|
||||
useEffect(() => {
|
||||
cacheRef.current = { entries: [] }
|
||||
cacheRef.current = { entries: [], live: [] }
|
||||
prevRowsRef.current = []
|
||||
controllerRef.current?.abort()
|
||||
setError(null)
|
||||
@@ -532,8 +539,24 @@ export function SideChatView(props: {
|
||||
// The previous poll's rows ride into the mapping so unchanged rows keep
|
||||
// their object identity (see reuseRows): the 2s poll re-renders only the
|
||||
// changed tail instead of re-parsing markdown for the whole transcript.
|
||||
// The live deltas are appended AFTER the durable events (they are the
|
||||
// in-flight tail), and a delta whose step already settled durably is
|
||||
// dropped: the durable message is authoritative and would otherwise render
|
||||
// twice.
|
||||
const rows = useMemo(() => {
|
||||
const next = threadId === undefined ? [] : transcriptRows(cacheRef.current.entries, prevRowsRef.current)
|
||||
const cache = cacheRef.current
|
||||
const settled = new Set(
|
||||
cache.entries
|
||||
.filter(entry => entry.event.type === 'assistant/message')
|
||||
.map(entry => {
|
||||
const data = entry.event.data as { turn?: unknown; step?: unknown }
|
||||
return `${String(data.turn)}:${String(data.step)}`
|
||||
}),
|
||||
)
|
||||
const live = cache.live
|
||||
.filter(event => !settled.has(`${String(event.data.turn)}:${String(event.data.step)}`))
|
||||
.map(event => ({ event: event as SidebarSessionEvent }))
|
||||
const next = threadId === undefined ? [] : transcriptRows([...cache.entries, ...live], prevRowsRef.current)
|
||||
prevRowsRef.current = next
|
||||
return next
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user