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:
@@ -901,17 +901,6 @@ export function SideCardSection({ store, service }: SideCardSectionProps) {
|
||||
<span className={css.suffix}>{t('settingsWidthSuffix')}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className={css.row}>
|
||||
<span className={css.rowText}>
|
||||
<span className={css.title}>{t('settingsOpenPathTitle')}</span>
|
||||
<span className={css.desc}>{t('settingsOpenPathDesc')}</span>
|
||||
</span>
|
||||
<Switch
|
||||
label={t('settingsOpenPathTitle')}
|
||||
checked={prefs.interceptOpenPath}
|
||||
onChange={(next) => { applyPref({ interceptOpenPath: next }) }}
|
||||
/>
|
||||
</div>
|
||||
<div className={css.row}>
|
||||
<span className={css.rowText}>
|
||||
<span className={css.title}>{t('settingsOpenToolsTitle')}</span>
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
+6
-3
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
import { encodeHtmlUrl } from '../html-route.ts'
|
||||
import type { LastActivity } from '../subagent-activity.ts'
|
||||
import type { SidechatLogEvent, SidechatThreadInfo } from '../sidechat-core.ts'
|
||||
import type { SidechatLiveEvent, SidechatLogEvent, SidechatThreadInfo } from '../sidechat-core.ts'
|
||||
import type { SidebarSessionEvent } from '../context-types.ts'
|
||||
import type { BrowserProbeResult } from './browser.ts'
|
||||
|
||||
@@ -390,9 +390,12 @@ export const api = {
|
||||
call<SidechatThreadInfo>('sidechat.info', { childId }),
|
||||
/** One transcript pull of a Side Chat thread: the thread's OWN events
|
||||
* (the inherited seed is cut host-side and never crosses the wire).
|
||||
* `afterSeq` narrows the response to the delta beyond it (poll tail). */
|
||||
* `afterSeq` narrows the response to the delta beyond it (poll tail).
|
||||
* `live` is the thread's in-flight model deltas (DSH 0.1.5 publishes them
|
||||
* outside the session log) — the CURRENT attempt on every pull, never a
|
||||
* delta, so the caller replaces its live set instead of appending. */
|
||||
sidechatEvents: (childId: string, afterSeq?: number, signal?: AbortSignal) =>
|
||||
call<{ events: SidechatLogEvent[] }>('sidechat.events', {
|
||||
call<{ events: SidechatLogEvent[]; live: SidechatLiveEvent[] }>('sidechat.events', {
|
||||
childId,
|
||||
...(afterSeq !== undefined ? { afterSeq } : {}),
|
||||
}, signal),
|
||||
|
||||
+1
-13
@@ -17,7 +17,7 @@ import { revalidateChunksOnReactivate, setChunkModuleSystem } from './chunk-load
|
||||
import { registerBuiltins } from './builtins/index.ts'
|
||||
import { Sidebar } from './Sidebar.tsx'
|
||||
import { RenderBoundary } from './RenderBoundary.tsx'
|
||||
import { registerOpenPathInterception, registerTurnTailInterception } from './intercept.tsx'
|
||||
import { registerTurnTailInterception } from './intercept.tsx'
|
||||
import { registerLinkInterception } from './link-intercept.ts'
|
||||
import { registerImeGuard } from './ime-guard.ts'
|
||||
import { registerSettingsNavIcon } from './settings-nav-icon.ts'
|
||||
@@ -342,18 +342,6 @@ export function apply(ctx: Context): void {
|
||||
'dsh-better-sidebar: turn-tail interception',
|
||||
)
|
||||
|
||||
ctx.effect(
|
||||
() => {
|
||||
try {
|
||||
return registerOpenPathInterception(ctx, sidebarStore)
|
||||
} catch (error) {
|
||||
fail('interception', error)
|
||||
return () => {}
|
||||
}
|
||||
},
|
||||
'dsh-better-sidebar: open-path interception',
|
||||
)
|
||||
|
||||
ctx.effect(
|
||||
() => {
|
||||
try {
|
||||
|
||||
@@ -11,7 +11,6 @@ import type { Context } from '../context-types.ts'
|
||||
import { firstLeaf, revealPaths, togglePanel, type SidebarStore } from './state.ts'
|
||||
import { t } from './locales.ts'
|
||||
import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts'
|
||||
import { wrapOpenWorkspacePath, type OpenWorkspacePathService } from './openpath-intercept.ts'
|
||||
import css from './sidebar.module.css'
|
||||
|
||||
/** Open a file in the sidebar's editor (used by the intercepted row and the explorer). */
|
||||
@@ -25,13 +24,6 @@ export function openSidebarFile(ctx: Context, store: SidebarStore, sessionId: st
|
||||
ctx.get('betterSidebar')?.openTab({ type: 'editor', title, path: absolute, id: `editor:${absolute}` })
|
||||
}
|
||||
|
||||
/**
|
||||
* The produced files the turn-tail selector last matched for the visible
|
||||
* session. The "Show in folder" gesture carries no file path of its own
|
||||
* (`'.'`), so the reveal highlights exactly these rows when available.
|
||||
*/
|
||||
let lastProduced: readonly string[] = []
|
||||
|
||||
/**
|
||||
* Reveal the produced files in the sidebar explorer: expand their parent
|
||||
* directories, highlight the rows, and focus the explorer tab (expanding the
|
||||
@@ -137,9 +129,7 @@ export function registerTurnTailInterception(ctx: Context, store: SidebarStore):
|
||||
select: (owner) => {
|
||||
if (store.getSuspended()) return null
|
||||
if (store.getPrefs().tabsEnabled['editor'] === false) return null
|
||||
const matched = selectProducedFiles(owner)
|
||||
if (matched !== null) lastProduced = matched
|
||||
return matched
|
||||
return selectProducedFiles(owner)
|
||||
},
|
||||
priority: -1,
|
||||
registrant: 'dsh-better-sidebar',
|
||||
@@ -149,38 +139,3 @@ export function registerTurnTailInterception(ctx: Context, store: SidebarStore):
|
||||
}),
|
||||
}, SidebarProducedFiles))
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the chat file-open interception: shadows
|
||||
* `remote.session.openWorkspacePath` — the single funnel every chat-side
|
||||
* file open goes through on alpha hosts (tool-row path links, the
|
||||
* produced-files row, prose mentions, inline-code paths) — so opens land in
|
||||
* the sidebar editor instead of the Host OS. The folder-reveal gesture
|
||||
* ("Show in folder" passes `'.'`) is the one exception: it is routed to the
|
||||
* explorer. Gated by BOTH the `interceptOpenPath` pref and the editor tab's
|
||||
* enable switch; declined opens fall through to the original remote call.
|
||||
*
|
||||
* The `remote.session` namespace service mounts asynchronously (the gateway
|
||||
* client creates it when the session-controller contribution arrives) and
|
||||
* is recreated on contribution remounts, so the wrapper installs through
|
||||
* `ctx.inject`: the callback runs once the service exists and re-runs after
|
||||
* every remount, re-applying the shadow on the fresh instance. Returns the
|
||||
* disposer (disposes the inject fiber, which restores the original method
|
||||
* descriptor — HMR-safe).
|
||||
*/
|
||||
export function registerOpenPathInterception(ctx: Context, store: SidebarStore): () => void {
|
||||
const fiber = ctx.inject(['remote.session'], (fctx) => {
|
||||
fctx.effect(() => {
|
||||
const service = fctx.get('remote.session') as OpenWorkspacePathService
|
||||
return wrapOpenWorkspacePath(service, {
|
||||
takeoverEnabled: () => !store.getSuspended()
|
||||
&& store.getPrefs().interceptOpenPath !== false
|
||||
&& store.getPrefs().tabsEnabled['editor'] !== false,
|
||||
currentSessionId: () => ctx.sessions.list.getSnapshot().current,
|
||||
openInSidebar: (path, sessionId) => { openSidebarFile(ctx, store, sessionId, path) },
|
||||
revealInExplorer: (_path, sessionId) => { revealInExplorer(ctx, store, sessionId, lastProduced) },
|
||||
})
|
||||
}, 'dsh-better-sidebar: open-path interception wrap')
|
||||
})
|
||||
return () => { void fiber.dispose() }
|
||||
}
|
||||
|
||||
@@ -238,8 +238,6 @@ export const ar: Record<string, string> = {
|
||||
settingsWidthTitle: 'الحصة الافتراضية للعرض',
|
||||
settingsWidthDesc: 'الحصة الافتراضية للبطاقة الجانبية من عرض النافذة للمحادثات الجديدة (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'فتح ملفات المحادثة في الشريط الجانبي',
|
||||
settingsOpenPathDesc: 'فتح روابط الملفات في المحادثة (صفوف الأدوات، الملفات الناتجة، الإشارات) في محرّر الشريط الجانبي بدلاً من التطبيق الافتراضي للنظام',
|
||||
settingsOpenToolsTitle: 'حقن أداة فتح الشريط الجانبي للنموذج',
|
||||
settingsOpenToolsDesc: 'عند التفعيل، يمكن للنموذج فتح الملفات والمجلدات وصفحات HTTP(S) في الشريط الجانبي عبر أداة sidebar_open (معطّل افتراضياً)',
|
||||
settingsTitleBarTitle: 'وضع توافق الموضع',
|
||||
|
||||
@@ -223,8 +223,6 @@ export const de: Record<string, string> = {
|
||||
settingsWidthTitle: 'Standardbreite',
|
||||
settingsWidthDesc: 'Standardanteil der Seitenkarte an der Fensterbreite für neue Unterhaltungen (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Chat-Dateien in der Seitenleiste öffnen',
|
||||
settingsOpenPathDesc: 'Dateilinks im Chat (Werkzeugzeilen, erstellte Dateien, Erwähnungen) werden im Seitenleisten-Editor geöffnet statt in der System-Standardanwendung',
|
||||
settingsOpenToolsTitle: 'Seitenleisten-Öffnungswerkzeug für das Modell bereitstellen',
|
||||
settingsOpenToolsDesc: 'Wenn aktiviert, kann das Modell über das sidebar_open-Werkzeug Dateien, Ordner und HTTP(S)-Seiten in der Seitenleiste öffnen (standardmäßig deaktiviert)',
|
||||
settingsTitleBarTitle: 'Kompatibilitätsmodus der Position',
|
||||
|
||||
@@ -230,8 +230,6 @@ export const fr: Record<string, string> = {
|
||||
settingsWidthTitle: 'Part de largeur par défaut',
|
||||
settingsWidthDesc: 'Pourcentage de la largeur de la fenêtre occupé par la carte latérale pour les nouvelles sessions (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Ouvrir les fichiers de discussion dans la barre latérale',
|
||||
settingsOpenPathDesc: 'À la place de l’application par défaut du système, ouvrir dans l’éditeur de la barre latérale les liens vers les fichiers du chat (lignes d’outils, listes de produits, mentions de fichiers)',
|
||||
settingsOpenToolsTitle: 'Injecter l\'outil d\'ouverture latérale pour le modèle',
|
||||
settingsOpenToolsDesc: 'Une fois activé, le modèle peut ouvrir des fichiers, dossiers et pages HTTP(S) dans la barre latérale via l\'outil sidebar_open (désactivé par défaut)',
|
||||
settingsTitleBarTitle: 'Mode de compatibilité de position',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const hi: Record<string, string> = {
|
||||
settingsWidthTitle: 'डिफ़ॉल्ट चौड़ाई हिस्सा',
|
||||
settingsWidthDesc: 'नए वार्तालाप के लिए साइड कार्ड की विंडो चौड़ाई का डिफ़ॉल्ट हिस्सा (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'चैट फ़ाइलें साइडबार में खोलें',
|
||||
settingsOpenPathDesc: 'चैट में फ़ाइल लिंक (टूल पंक्ति, उत्पादित फ़ाइलें, उल्लेख) क्लिक करने पर सिस्टम डिफ़ॉल्ट ऐप के बजाय साइडबार एडिटर में खोलें',
|
||||
settingsOpenToolsTitle: 'मॉडल के लिए साइडबार ओपन टूल इंजेक्ट करें',
|
||||
settingsOpenToolsDesc: 'चालू होने पर, मॉडल sidebar_open टूल से साइडबार में फ़ाइलें, फ़ोल्डर और HTTP(S) पेज खोल सकता है (डिफ़ॉल्ट रूप से बंद)',
|
||||
settingsTitleBarTitle: 'स्थिति संगतता मोड',
|
||||
|
||||
@@ -235,8 +235,6 @@ export const id: Record<string, string> = {
|
||||
settingsWidthTitle: 'Porsi lebar default',
|
||||
settingsWidthDesc: 'Porsi lebar jendela default kartu samping untuk obrolan baru (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Buka berkas obrolan di sidebar',
|
||||
settingsOpenPathDesc: 'Buka tautan berkas di obrolan (baris alat, berkas yang dihasilkan, mention) di editor sidebar alih-alih aplikasi default sistem',
|
||||
settingsOpenToolsTitle: 'Suntik alat buka sidebar untuk model',
|
||||
settingsOpenToolsDesc: 'Saat diaktifkan, model dapat membuka file, folder, dan halaman HTTP(S) di sidebar melalui alat sidebar_open (nonaktif secara default)',
|
||||
settingsTitleBarTitle: 'Mode kompatibilitas posisi',
|
||||
|
||||
@@ -228,8 +228,6 @@ export const it: Record<string, string> = {
|
||||
settingsWidthTitle: 'Quota di larghezza predefinita',
|
||||
settingsWidthDesc: 'La quota predefinita della scheda laterale sulla larghezza della finestra per le nuove conversazioni (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Apri i file della chat nella barra laterale',
|
||||
settingsOpenPathDesc: 'Apre i collegamenti ai file nella chat (righe di strumenti, file prodotti, menzioni) nell’editor della barra laterale invece dell’app predefinita di sistema',
|
||||
settingsOpenToolsTitle: 'Iniettare lo strumento di apertura della barra laterale per il modello',
|
||||
settingsOpenToolsDesc: 'Se attivato, il modello può aprire file, cartelle e pagine HTTP(S) nella barra laterale tramite lo strumento sidebar_open (disattivato per impostazione predefinita)',
|
||||
settingsTitleBarTitle: 'Modalità di compatibilità della posizione',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const ja: Record<string, string> = {
|
||||
settingsWidthTitle: 'デフォルト幅の比率',
|
||||
settingsWidthDesc: '新規会話時のサイドカードがウィンドウ幅に占める割合 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'チャット内ファイルをサイドバーで開く',
|
||||
settingsOpenPathDesc: 'チャット内のファイルリンク(ツール行、産物リスト、ファイル言及)クリック時に、システム既定アプリではなくサイドバーエディターで開く',
|
||||
settingsOpenToolsTitle: 'モデルにサイドバー開くツールを注入',
|
||||
settingsOpenToolsDesc: 'オンにすると、モデルは sidebar_open ツールでサイドバーにファイル・フォルダー・HTTP(S) ページを開ける(デフォルトオフ)',
|
||||
settingsTitleBarTitle: '位置互換モード',
|
||||
|
||||
@@ -229,8 +229,6 @@ export const ko: Record<string, string> = {
|
||||
settingsWidthTitle: '기본 너비 비율',
|
||||
settingsWidthDesc: '새 대화에서 사이드 카드가 창 너비에서 차지하는 비율(%) (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '채팅 영역 파일을 사이드바에서 열기',
|
||||
settingsOpenPathDesc: '채팅에서 파일 링크(도구 행, 산출물 목록, 파일 언급)를 클릭하면 사이드바 편집기에서 열고, 시스템 기본 앱은 호출하지 않습니다',
|
||||
settingsOpenToolsTitle: '모델에 사이드바 열기 도구 주입',
|
||||
settingsOpenToolsDesc: '켜면 모델이 sidebar_open 도구로 사이드바에서 파일·폴더·HTTP(S) 페이지를 열 수 있습니다(기본 꺼짐)',
|
||||
settingsTitleBarTitle: '위치 호환 모드',
|
||||
|
||||
@@ -235,8 +235,6 @@ export const nl: Record<string, string> = {
|
||||
settingsWidthTitle: 'Standaard breedte-aandeel',
|
||||
settingsWidthDesc: 'Het standaard breedte-aandeel van de zijkaart voor nieuwe conversaties (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Chatbestanden in de zijbalk openen',
|
||||
settingsOpenPathDesc: 'Bestandslinks in de chat (toolrijen, geproduceerde bestanden, vermeldingen) openen in de zijbalk-editor in plaats van de standaard systeemapp',
|
||||
settingsOpenToolsTitle: 'Zijbalk-openen-tool voor het model injecteren',
|
||||
settingsOpenToolsDesc: 'Indien ingeschakeld kan het model bestanden, mappen en HTTP(S)-pagina\'s in de zijbalk openen via de sidebar_open-tool (standaard uit)',
|
||||
settingsTitleBarTitle: 'Positiecompatibiliteitsmodus',
|
||||
|
||||
@@ -239,8 +239,6 @@ export const pl: Record<string, string> = {
|
||||
settingsWidthTitle: 'Domyślny udział szerokości',
|
||||
settingsWidthDesc: 'Domyślny udział szerokości okna dla karty bocznej w nowych rozmowach (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Otwieraj pliki czatu w panelu bocznym',
|
||||
settingsOpenPathDesc: 'Otwieraj linki plików na czacie (wiersze narzędzi, wyprodukowane pliki, wzmianki) w edytorze panelu bocznego zamiast w domyślnej aplikacji systemu',
|
||||
settingsOpenToolsTitle: 'Wstrzyknij narzędzie otwierania panelu bocznego dla modelu',
|
||||
settingsOpenToolsDesc: 'Po włączeniu model może otwierać pliki, foldery i strony HTTP(S) w panelu bocznym za pomocą narzędzia sidebar_open (domyślnie wyłączone)',
|
||||
settingsTitleBarTitle: 'Tryb zgodności pozycji',
|
||||
|
||||
@@ -220,8 +220,6 @@ export const pt: Record<string, string> = {
|
||||
settingsWidthTitle: 'Participação padrão na largura',
|
||||
settingsWidthDesc: 'Participação padrão do cartão lateral na largura da janela em novas conversas (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Abrir arquivos do chat na barra lateral',
|
||||
settingsOpenPathDesc: 'Abrir links de arquivos no chat (linhas de ferramentas, arquivos produzidos, menções) no editor da barra lateral em vez do aplicativo padrão do sistema',
|
||||
settingsOpenToolsTitle: 'Injetar a ferramenta de abertura na barra lateral para o modelo',
|
||||
settingsOpenToolsDesc: 'Quando ativado, o modelo pode abrir arquivos, pastas e páginas HTTP(S) na barra lateral pela ferramenta sidebar_open (desativado por padrão)',
|
||||
settingsTitleBarTitle: 'Modo de compatibilidade de posição',
|
||||
|
||||
@@ -234,8 +234,6 @@ export const ru: Record<string, string> = {
|
||||
settingsWidthTitle: 'Доля ширины по умолчанию',
|
||||
settingsWidthDesc: 'Доля боковой карточки в ширине окна для новых сессий, в процентах (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Открывать файлы чата в боковой панели',
|
||||
settingsOpenPathDesc: 'При щелчке по ссылке на файл в чате (строки инструментов, списки результатов, упоминания) файл открывается в редакторе боковой панели, а не в системном приложении по умолчанию',
|
||||
settingsOpenToolsTitle: 'Внедрить инструмент открытия в боковой панели для модели',
|
||||
settingsOpenToolsDesc: 'Если включено, модель может открывать файлы, папки и HTTP(S)-страницы в боковой панели с помощью инструмента sidebar_open (по умолчанию выключено)',
|
||||
settingsTitleBarTitle: 'Режим совместимости заголовка',
|
||||
|
||||
@@ -220,8 +220,6 @@ export const sv: Record<string, string> = {
|
||||
settingsWidthTitle: 'Standardbreddandel',
|
||||
settingsWidthDesc: 'Sidokortets standardandel av fönsterbredden för nya konversationer (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Öppna chattfiler i sidopanelen',
|
||||
settingsOpenPathDesc: 'Öppna fillänkar i chatten (verktygsrader, producerade filer, omnämnanden) i sidopanelens editor i stället för systemets standardapp',
|
||||
settingsOpenToolsTitle: 'Injicera sidopanel-öppningsverktyg för modellen',
|
||||
settingsOpenToolsDesc: 'När aktiverat kan modellen öppna filer, mappar och HTTP(S)-sidor i sidopanelen via sidebar_open-verktyget (av som standard)',
|
||||
settingsTitleBarTitle: 'Positions kompatibilitetsläge',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const th: Record<string, string> = {
|
||||
settingsWidthTitle: 'สัดส่วนความกว้างเริ่มต้น',
|
||||
settingsWidthDesc: 'สัดส่วนความกว้างเริ่มต้นของการ์ดด้านข้างจากความกว้างหน้าต่างสำหรับแชทใหม่ (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'เปิดไฟล์แชทในแถบด้านข้าง',
|
||||
settingsOpenPathDesc: 'เปิดลิงก์ไฟล์ในแชท (แถวเครื่องมือ, ไฟล์ที่สร้าง, การกล่าวถึง) ในตัวแก้ไขแถบด้านข้างแทนแอปเริ่มต้นของระบบ',
|
||||
settingsOpenToolsTitle: 'ฉีดเครื่องมือเปิดแถบด้านข้างสำหรับโมเดล',
|
||||
settingsOpenToolsDesc: 'เมื่อเปิดใช้ โมเดลสามารถเปิดไฟล์ โฟลเดอร์ และหน้า HTTP(S) ในแถบด้านข้างผ่านเครื่องมือ sidebar_open (ปิดเป็นค่าเริ่มต้น)',
|
||||
settingsTitleBarTitle: 'โหมดความเข้ากันได้ของตำแหน่ง',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const tr: Record<string, string> = {
|
||||
settingsWidthTitle: 'Varsayılan genişlik payı',
|
||||
settingsWidthDesc: 'Yeni sohbetler için yan kartın pencere genişliğindeki varsayılan payı (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Sohbet dosyalarını kenar çubuğunda aç',
|
||||
settingsOpenPathDesc: 'Sohbetteki dosya bağlantılarını (araç satırları, üretilen dosyalar, anılmalar) sistem varsayılan uygulaması yerine kenar çubuğu düzenleyicisinde aç',
|
||||
settingsOpenToolsTitle: 'Model için kenar çubuğu açma aracı enjekte et',
|
||||
settingsOpenToolsDesc: 'Etkinleştirildiğinde model, sidebar_open aracıyla kenar çubuğunda dosyaları, klasörleri ve HTTP(S) sayfalarını açabilir (varsayılan kapalı)',
|
||||
settingsTitleBarTitle: 'Konum uyumluluk modu',
|
||||
|
||||
@@ -237,8 +237,6 @@ export const vi: Record<string, string> = {
|
||||
settingsWidthTitle: 'Tỷ lệ chiều rộng mặc định',
|
||||
settingsWidthDesc: 'Tỷ lệ phần trăm chiều rộng cửa sổ mà thẻ bên chiếm cho phiên mới (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Mở tệp chat trong thanh bên',
|
||||
settingsOpenPathDesc: 'Khi nhấp link tệp trong chat (dòng công cụ, danh sách sản phẩm, nhắc tệp), mở trong trình soạn thảo thanh bên thay vì ứng dụng mặc định hệ thống',
|
||||
settingsOpenToolsTitle: 'Tiêm công cụ mở thanh bên cho mô hình',
|
||||
settingsOpenToolsDesc: 'Khi bật, mô hình có thể mở tệp, thư mục và trang HTTP(S) trong thanh bên qua công cụ sidebar_open (mặc định tắt)',
|
||||
settingsTitleBarTitle: 'Chế độ tương thích thanh tiêu đề',
|
||||
|
||||
@@ -252,8 +252,6 @@ export const zhHK: Record<string, string> = {
|
||||
settingsWidthTitle: '預設寬度佔比',
|
||||
settingsWidthDesc: '新增工作階段時側邊卡片佔視窗寬度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天區檔案在側邊欄開啟',
|
||||
settingsOpenPathDesc: '在聊天裡點擊檔案連結(工具行、產物列表、檔案提及)時,在側邊欄編輯器中開啟,不再呼叫系統預設應用程式',
|
||||
settingsOpenToolsTitle: '為模型注入側邊欄開啟工具',
|
||||
settingsOpenToolsDesc: '開啟後,模型可透過 sidebar_open 工具在側邊欄主動開啟檔案、資料夾和 HTTP(S) 網頁(預設關閉)',
|
||||
settingsTitleBarTitle: '位置相容模式',
|
||||
|
||||
@@ -252,8 +252,6 @@ export const zhMO: Record<string, string> = {
|
||||
settingsWidthTitle: '預設寬度佔比',
|
||||
settingsWidthDesc: '新增工作階段時側邊卡片佔視窗寬度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天區檔案在側邊欄開啟',
|
||||
settingsOpenPathDesc: '在聊天裡點擊檔案連結(工具行、產物列表、檔案提及)時,在側邊欄編輯器中開啟,不再呼叫系統預設應用程式',
|
||||
settingsOpenToolsTitle: '為模型注入側邊欄開啟工具',
|
||||
settingsOpenToolsDesc: '開啟後,模型可透過 sidebar_open 工具在側邊欄主動開啟檔案、資料夾和 HTTP(S) 網頁(預設關閉)',
|
||||
settingsTitleBarTitle: '位置相容模式',
|
||||
|
||||
@@ -252,8 +252,6 @@ export const zhTW: Record<string, string> = {
|
||||
settingsWidthTitle: '預設寬度佔比',
|
||||
settingsWidthDesc: '新增工作階段時側邊卡片佔視窗寬度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天區檔案在側邊欄開啟',
|
||||
settingsOpenPathDesc: '在聊天裡點擊檔案連結(工具行、產物列表、檔案提及)時,在側邊欄編輯器中開啟,不再呼叫系統預設應用程式',
|
||||
settingsOpenToolsTitle: '為模型注入側邊欄開啟工具',
|
||||
settingsOpenToolsDesc: '開啟後,模型可透過 sidebar_open 工具在側邊欄主動開啟檔案、資料夾和 HTTP(S) 網頁(預設關閉)',
|
||||
settingsTitleBarTitle: '位置相容模式',
|
||||
|
||||
@@ -249,8 +249,6 @@ export const zh = {
|
||||
settingsWidthTitle: '默认宽度占比',
|
||||
settingsWidthDesc: '新建会话时侧边卡片占窗口宽度的百分比 (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: '聊天区文件在侧边栏打开',
|
||||
settingsOpenPathDesc: '在聊天里点击文件链接(工具行、产物列表、文件提及)时,在侧边栏编辑器中打开,不再调用系统默认应用',
|
||||
settingsOpenToolsTitle: '为模型注入侧边栏打开工具',
|
||||
settingsOpenToolsDesc: '开启后,模型可通过 sidebar_open 工具在侧边栏主动打开文件、文件夹和 HTTP(S) 网页(默认关闭)',
|
||||
settingsTitleBarTitle: '位置兼容模式',
|
||||
@@ -699,8 +697,6 @@ export const en: Record<keyof typeof zh, string> = {
|
||||
settingsWidthTitle: 'Default width share',
|
||||
settingsWidthDesc: 'The side card\'s default share of the window width for new conversations (20–60)',
|
||||
settingsWidthSuffix: '%',
|
||||
settingsOpenPathTitle: 'Open chat files in the sidebar',
|
||||
settingsOpenPathDesc: 'Open file links in the chat (tool rows, produced files, mentions) in the sidebar editor instead of the system default app',
|
||||
settingsOpenToolsTitle: 'Inject the sidebar-open tool for the model',
|
||||
settingsOpenToolsDesc: 'When enabled, the model can actively open files, folders, and HTTP(S) pages in the sidebar through the sidebar_open tool (off by default)',
|
||||
settingsTitleBarTitle: 'Position compatibility mode',
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/**
|
||||
* Interception of the chat's file-open funnel (DSH 0.1.2-alpha hosts).
|
||||
*
|
||||
* The alpha funnel is the Typert remote namespace `remote.session`: every
|
||||
* chat-side file open (tool-row path links, the produced-files row, prose
|
||||
* file mentions, inline-code paths) funnels through ChatView's injected
|
||||
* `openFile`, which calls `ctx.remote.session.openWorkspacePath({ path })`
|
||||
* with the path ALREADY resolved against the session cwd (verified against
|
||||
* the DSH source: `packages/client/ui-chat/src/client/apply.ts` is the only
|
||||
* production caller). The host side (`api/session-controller`) hands the
|
||||
* path to the OS's default application (`openNativePath` → xdg-open on
|
||||
* Linux). The pre-alpha funnel `ctx.workspaces.openPath` is gone — the
|
||||
* alpha `IWorkspaces` has no openPath at all.
|
||||
*
|
||||
* `remote.session` is a cordis service (the gateway client's
|
||||
* RemoteNamespaceService, key `remote.session`) whose methods are installed
|
||||
* as ACCESSOR properties (`Object.defineProperty(service, method, {
|
||||
* configurable: true, get })`) — the getter reads the method table at
|
||||
* ACCESS time and returns the invocation closure. A plain assignment would
|
||||
* not stick (no setter), so the wrapper shadows the method with a data
|
||||
* property and restores the captured accessor descriptor on dispose.
|
||||
*
|
||||
* The namespace service appears asynchronously (the session-controller
|
||||
* contribution mounts after the connection is up) and is disposed/recreated
|
||||
* on contribution remounts; the caller therefore enters through
|
||||
* `ctx.inject(['remote.session'], …)`, which re-fires on every remount.
|
||||
*
|
||||
* The wrapper is dependency-free by design (no React / ui-primitives), so
|
||||
* the takeover logic is unit-testable and the file stays importable from
|
||||
* the test runtime.
|
||||
*/
|
||||
|
||||
/** The one request shape the funnel method takes (mirror of the host's
|
||||
* SessionOpenWorkspacePathRequest). */
|
||||
export interface OpenWorkspacePathRequest {
|
||||
/** Absolute path (the chat caller already resolved it against the cwd). */
|
||||
path: string
|
||||
}
|
||||
|
||||
/**
|
||||
* What the funnel method resolves to (mirror of the typert `RemoteResult`
|
||||
* envelope): EVERY remote method folds its business value into
|
||||
* `{ ok: true, value }` / `{ ok: false, error }` — the chat caller branches
|
||||
* on `result.ok`, so a bare business value would be misread as a failure
|
||||
* (`result.error.message` reads `message` off undefined).
|
||||
*/
|
||||
export type OpenWorkspacePathResult =
|
||||
| { readonly ok: true; readonly value: { opened: boolean } }
|
||||
| { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: object } }
|
||||
|
||||
/** The namespace slice the wrapper shadows (mirror of the gateway client's
|
||||
* RemoteNamespaceService for the `session` namespace). */
|
||||
export interface OpenWorkspacePathService {
|
||||
openWorkspacePath(request: OpenWorkspacePathRequest, signal?: AbortSignal): Promise<OpenWorkspacePathResult>
|
||||
}
|
||||
|
||||
/** Per-call decisions the wrapper needs (wired to the store + ctx in the client half). */
|
||||
export interface OpenPathInterceptDeps {
|
||||
/**
|
||||
* Whether to take over this call: the `interceptOpenPath` pref AND the
|
||||
* editor tab's own enable switch must both be on (an editor that cannot
|
||||
* open must not swallow opens — they fall through to the Host).
|
||||
*/
|
||||
takeoverEnabled(): boolean
|
||||
/** The session whose scope the sidebar editor loads the file in (current session). */
|
||||
currentSessionId(): string | undefined
|
||||
/** Route the open into the sidebar editor (the established openSidebarFile). */
|
||||
openInSidebar(path: string, sessionId: string): void
|
||||
/** Route a folder-reveal gesture ("Show in folder" passes '.') into the sidebar explorer. */
|
||||
revealInExplorer(path: string, sessionId: string): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a path is the "Show in folder" folder-reveal gesture. The stock
|
||||
* ui-deliverables row passes `'.'` (the session workspace root, resolved by
|
||||
* the chat view to `"<cwd>/."`); any path whose final segment is `.` is the
|
||||
* same gesture. A directory has no editor content, so these opens must reach
|
||||
* the explorer instead of an editor tab.
|
||||
*/
|
||||
export function isFolderRevealPath(path: string): boolean {
|
||||
if (path === '.' || path === './') return true
|
||||
const trimmed = path.replace(/[\\/]+$/, '')
|
||||
return trimmed === '.' || /[\\/]\.$/.test(trimmed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shadow `remote.session.openWorkspacePath`: intercepted calls open the file
|
||||
* in the sidebar editor and resolve with the remote SUCCESS ENVELOPE
|
||||
* (`{ ok: true, value: { opened: true } }`, so ChatView shows no error
|
||||
* dialog); anything that declines falls through to the captured original
|
||||
* closure (the host OS's default application) untouched.
|
||||
* The one exception is the folder-reveal gesture, which is routed to
|
||||
* {@link OpenPathInterceptDeps.revealInExplorer} instead.
|
||||
*
|
||||
* The original closure is captured by ACCESSING the accessor once at wrap
|
||||
* time — it invokes whatever method records are mounted at that moment. If
|
||||
* the contribution remounts its methods while the shadow is installed, the
|
||||
* captured closure points at the old records; the session namespace is
|
||||
* effectively permanent in practice, so this is accepted and the shadow is
|
||||
* re-applied anyway when the remount recreates the service (the caller's
|
||||
* `ctx.inject` re-fires).
|
||||
*
|
||||
* @param service - the `remote.session` namespace service.
|
||||
* @param deps - per-call takeover decisions.
|
||||
* @returns the disposer restoring the original accessor descriptor (HMR-safe).
|
||||
*/
|
||||
export function wrapOpenWorkspacePath(
|
||||
service: OpenWorkspacePathService,
|
||||
deps: OpenPathInterceptDeps,
|
||||
): () => void {
|
||||
const KEY = 'openWorkspacePath'
|
||||
const target = service as object
|
||||
const descriptor = Object.getOwnPropertyDescriptor(target, KEY)
|
||||
// Invoke the accessor once to capture the CURRENT invocation closure.
|
||||
const original = service.openWorkspacePath
|
||||
// The gateway installs the namespace's whole method group in the same
|
||||
// synchronous window as the service registration, so an inject-observed
|
||||
// service always has the method — but a hand-rolled composition might not;
|
||||
// wrapping nothing would turn fall-throughs into crashes, so decline.
|
||||
if (typeof original !== 'function') return () => {}
|
||||
const wrapped = (request: OpenWorkspacePathRequest, signal?: AbortSignal): Promise<OpenWorkspacePathResult> => {
|
||||
if (deps.takeoverEnabled()) {
|
||||
const sessionId = deps.currentSessionId()
|
||||
if (sessionId !== undefined) {
|
||||
if (isFolderRevealPath(request.path)) deps.revealInExplorer(request.path, sessionId)
|
||||
else deps.openInSidebar(request.path, sessionId)
|
||||
return Promise.resolve({ ok: true, value: { opened: true } })
|
||||
}
|
||||
}
|
||||
return original.call(service, request, signal)
|
||||
}
|
||||
Object.defineProperty(target, KEY, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: wrapped,
|
||||
})
|
||||
return () => {
|
||||
// Restore the exact original property (the gateway's accessor) so a
|
||||
// chain of wrappers keeps working across disposals in any order.
|
||||
if (descriptor !== undefined) Object.defineProperty(target, KEY, descriptor)
|
||||
else Reflect.deleteProperty(target, KEY)
|
||||
}
|
||||
}
|
||||
@@ -76,9 +76,6 @@ export function parsePrefs(value: unknown): SidebarPrefs {
|
||||
terminalFontSize: typeof record.terminalFontSize === 'number' && Number.isFinite(record.terminalFontSize)
|
||||
? clampTerminalFontSize(record.terminalFontSize)
|
||||
: SIDEBAR_PREFS_DEFAULTS.terminalFontSize,
|
||||
interceptOpenPath: typeof record.interceptOpenPath === 'boolean'
|
||||
? record.interceptOpenPath
|
||||
: SIDEBAR_PREFS_DEFAULTS.interceptOpenPath,
|
||||
editorExplorer: typeof record.editorExplorer === 'boolean'
|
||||
? record.editorExplorer
|
||||
: SIDEBAR_PREFS_DEFAULTS.editorExplorer,
|
||||
|
||||
@@ -465,7 +465,7 @@ export function matchUrlTarget(tabs: readonly TabDescriptor[], url: URL): TabDes
|
||||
* The plugin version this service instance reports. Keep in lockstep with
|
||||
* `package.json`'s version — `tests/service.spec.ts` asserts the pair.
|
||||
*/
|
||||
export const SIDEBAR_SERVICE_VERSION = '0.18.1'
|
||||
export const SIDEBAR_SERVICE_VERSION = '0.19.0-alpha.0'
|
||||
|
||||
/**
|
||||
* Monotonic capability list consumers use to gate new API usage (features
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* shows only the thread's own conversation.
|
||||
*
|
||||
* Live streaming: `assistant/message` events only land when a step
|
||||
* completes, but `assistant/chunk` events stream token-level text and
|
||||
* reasoning deltas. The mapping accumulates both per block and supersedes
|
||||
* them with the assembled message once it lands (settled rows).
|
||||
* completes, but the host's `assistant/live-chunk` rows stream token-level
|
||||
* text and reasoning deltas (DSH 0.1.5 publishes them outside the session
|
||||
* log — see assistant-live.ts). The mapping accumulates both per block and
|
||||
* supersedes them with the assembled message once it lands (settled rows).
|
||||
*/
|
||||
import type { DiffHunk, ReadBlockLine } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { SidebarHistoryEntry } from '../context-types.ts'
|
||||
@@ -299,12 +300,13 @@ function resultCard(
|
||||
/**
|
||||
* Map a thread child's history rows onto compact transcript rows: the
|
||||
* inherited fork seed is cut at the last `session/end-seed`, context
|
||||
* injections map onto a collapsible injection row, `assistant/chunk`
|
||||
* injections map onto a collapsible injection row, `assistant/live-chunk`
|
||||
* deltas accumulate into streaming rows per (turn, step, block) and are
|
||||
* superseded by the assembled `assistant/message`, and tool invocations
|
||||
* render one expandable line each (arguments, paired result text, failure
|
||||
* marker; a still-executing call is marked until its result lands).
|
||||
* @param entries - history rows (event + host-computed view) in seq order.
|
||||
* @param entries - history rows (event + host-computed view) in seq order,
|
||||
* the live deltas of the in-flight attempt appended after the durable tail.
|
||||
* @returns display rows in log order.
|
||||
*/
|
||||
export function transcriptRows(entries: readonly SidebarHistoryEntry[], prev?: readonly SidechatTranscriptRow[]): SidechatTranscriptRow[] {
|
||||
@@ -374,7 +376,7 @@ export function transcriptRows(entries: readonly SidebarHistoryEntry[], prev?: r
|
||||
rows.push({ kind: 'user', seq: event.seq, text })
|
||||
break
|
||||
}
|
||||
case 'assistant/chunk': {
|
||||
case 'assistant/live-chunk': {
|
||||
const chunk = data.chunk as { type?: unknown; text?: unknown } | undefined
|
||||
if (chunk === null || typeof chunk !== 'object') break
|
||||
const kind = chunk.type === 'text-delta' ? 'assistant' : chunk.type === 'reasoning-delta' ? 'reasoning' : null
|
||||
|
||||
Reference in New Issue
Block a user