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:
+26
-10
@@ -37,17 +37,21 @@ import type {
|
||||
import {
|
||||
boundaryDelivered,
|
||||
buildSidechatInheritance,
|
||||
liveEventsOf,
|
||||
resolvePresetId,
|
||||
SIDE_BOUNDARY_PROMPT,
|
||||
SIDE_INJECTION_PLUGIN,
|
||||
SIDE_NEW_THREAD_TITLE,
|
||||
sideLabel,
|
||||
type SeedEvent,
|
||||
type SidechatLiveEvent,
|
||||
type SidechatLogEvent,
|
||||
type SidechatThreadInfo,
|
||||
threadOwnLogEvents,
|
||||
} from './sidechat-core.ts'
|
||||
import type { AssistantLiveBuffer } from './assistant-live.ts'
|
||||
import { requireString, SidebarError } from './wire.ts'
|
||||
import { readPersistedSession } from './session-store.ts'
|
||||
|
||||
/** The six Side Chat routes of the sidebar API (wire method names). */
|
||||
export interface SidechatRoutes {
|
||||
@@ -66,8 +70,10 @@ export interface SidechatRoutes {
|
||||
'sidechat.info'(payload: unknown): Promise<SidechatThreadInfo>
|
||||
/** The thread's OWN transcript events, seed-cut host-side (the inherited
|
||||
* parent log never crosses the wire); `afterSeq` narrows the response to
|
||||
* the delta beyond it (poll tail). */
|
||||
'sidechat.events'(payload: unknown): Promise<{ events: SidechatLogEvent[] }>
|
||||
* the delta beyond it (poll tail). `live` carries the thread's in-flight
|
||||
* model deltas, which DSH 0.1.5 publishes outside the session log — it is
|
||||
* the CURRENT attempt's rows on every poll, never a delta. */
|
||||
'sidechat.events'(payload: unknown): Promise<{ events: SidechatLogEvent[]; live: SidechatLiveEvent[] }>
|
||||
}
|
||||
|
||||
/** Timeout guarding the create call (the registry detaches it before the
|
||||
@@ -117,8 +123,8 @@ async function composePersistedSetup(
|
||||
if (persistence === undefined) {
|
||||
return () => Promise.resolve()
|
||||
}
|
||||
const inspected = await persistence.inspect(childId)
|
||||
const presetId = resolvePresetId(inspected.meta, inspected.events)
|
||||
const inspected = await readPersistedSession(persistence, childId)
|
||||
const presetId = resolvePresetId(inspected.header, inspected.events)
|
||||
const presets = ctx.get('agentPresets') as SidebarAgentPresetsService | undefined
|
||||
if (presets === undefined || presetId === undefined) {
|
||||
return () => Promise.resolve()
|
||||
@@ -186,7 +192,7 @@ async function threadLogEvents(ctx: Context, childId: string): Promise<readonly
|
||||
throw new SidebarError('sidechat-error', 'the session persistence service is unavailable', 503)
|
||||
}
|
||||
try {
|
||||
const inspected = await persistence.inspect(childId)
|
||||
const inspected = await readPersistedSession(persistence, childId)
|
||||
return inspected.events as unknown as readonly SidechatLogEvent[]
|
||||
} catch (error: unknown) {
|
||||
throw new SidebarError(
|
||||
@@ -199,8 +205,11 @@ async function threadLogEvents(ctx: Context, childId: string): Promise<readonly
|
||||
|
||||
/** Build the Side Chat routes (all optional services degrade to a wire
|
||||
* error the tab surfaces inline). The record keys are the FULL wire method
|
||||
* names the /sidebar/api dispatcher looks up (`api[method]`). */
|
||||
export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
* names the /sidebar/api dispatcher looks up (`api[method]`).
|
||||
* @param ctx - host plugin context.
|
||||
* @param live - the live assistant stream buffer; absent only in tests that
|
||||
* never exercise streaming (then every `live` response is empty). */
|
||||
export function buildSidechatApi(ctx: Context, live?: AssistantLiveBuffer): SidechatRoutes {
|
||||
return {
|
||||
'sidechat.start': async (payload: unknown) => {
|
||||
const sessionId = requireString(payload, 'sessionId')
|
||||
@@ -213,6 +222,7 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
const parentSession = parent.session
|
||||
const inheritance = buildSidechatInheritance(
|
||||
parentSession.snapshotEvents() as unknown as readonly SidechatLogEvent[],
|
||||
live?.chunksFor(sessionId) ?? [],
|
||||
)
|
||||
const { agentPreset, setup } = await composeChildSetup(
|
||||
ctx,
|
||||
@@ -388,8 +398,8 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
const persistence = ctx.get('sessionPersistence') as SidebarSessionPersistenceService | undefined
|
||||
if (persistence !== undefined) {
|
||||
try {
|
||||
const inspected = await persistence.inspect(childId)
|
||||
const preset = resolvePresetId(inspected.meta, inspected.events)
|
||||
const inspected = await readPersistedSession(persistence, childId)
|
||||
const preset = resolvePresetId(inspected.header, inspected.events)
|
||||
return { live: false, ...(preset === undefined ? {} : { preset }) }
|
||||
} catch {
|
||||
// Unknown/gone session: report a bare cold info.
|
||||
@@ -408,7 +418,13 @@ export function buildSidechatApi(ctx: Context): SidechatRoutes {
|
||||
const events = await threadLogEvents(ctx, childId)
|
||||
const own = threadOwnLogEvents(events)
|
||||
const fresh = rawAfter === undefined ? own : own.filter(event => event.seq > rawAfter)
|
||||
return { events: fresh.length > EVENTS_CAP ? fresh.slice(fresh.length - EVENTS_CAP) : fresh }
|
||||
const tailSeq = own.at(-1)?.seq ?? -1
|
||||
return {
|
||||
events: fresh.length > EVENTS_CAP ? fresh.slice(fresh.length - EVENTS_CAP) : fresh,
|
||||
// The live rows ride every poll: they are process-local, so there is
|
||||
// no durable seq to page them by. An idle thread has none.
|
||||
live: liveEventsOf(live?.chunksFor(childId) ?? [], tailSeq),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user