@hpyperry/dsh-ref-lib 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/cordis.patch.yml +6 -0
- package/lib/client/RefLibBrowser.d.ts +35 -0
- package/lib/client/RefLibBrowser.d.ts.map +1 -0
- package/lib/client/RefLibBrowser.js +119 -0
- package/lib/client/RefLibBrowser.js.map +1 -0
- package/lib/client/RefLibDock.d.ts +44 -0
- package/lib/client/RefLibDock.d.ts.map +1 -0
- package/lib/client/RefLibDock.js +218 -0
- package/lib/client/RefLibDock.js.map +1 -0
- package/lib/client/RefLibPanel.d.ts +47 -0
- package/lib/client/RefLibPanel.d.ts.map +1 -0
- package/lib/client/RefLibPanel.js +59 -0
- package/lib/client/RefLibPanel.js.map +1 -0
- package/lib/client/data.d.ts +53 -0
- package/lib/client/data.d.ts.map +1 -0
- package/lib/client/data.js +74 -0
- package/lib/client/data.js.map +1 -0
- package/lib/client/index.d.ts +32 -0
- package/lib/client/index.d.ts.map +1 -0
- package/lib/client/index.js +80 -0
- package/lib/client/index.js.map +1 -0
- package/lib/client/layout.d.ts +34 -0
- package/lib/client/layout.d.ts.map +1 -0
- package/lib/client/layout.js +65 -0
- package/lib/client/layout.js.map +1 -0
- package/lib/client/locales.d.ts +80 -0
- package/lib/client/locales.d.ts.map +1 -0
- package/lib/client/locales.js +84 -0
- package/lib/client/locales.js.map +1 -0
- package/lib/client/styles.d.ts +15 -0
- package/lib/client/styles.d.ts.map +1 -0
- package/lib/client/styles.js +513 -0
- package/lib/client/styles.js.map +1 -0
- package/lib/client.js +1377 -0
- package/lib/client.js.map +1 -0
- package/lib/commands.d.ts +38 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +49 -0
- package/lib/commands.js.map +1 -0
- package/lib/index.d.ts +36 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +94 -0
- package/lib/index.js.map +1 -0
- package/lib/logic.d.ts +32 -0
- package/lib/logic.d.ts.map +1 -0
- package/lib/logic.js +48 -0
- package/lib/logic.js.map +1 -0
- package/lib/render.d.ts +18 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +40 -0
- package/lib/render.js.map +1 -0
- package/lib/routes.d.ts +39 -0
- package/lib/routes.d.ts.map +1 -0
- package/lib/routes.js +210 -0
- package/lib/routes.js.map +1 -0
- package/lib/service.d.ts +88 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +212 -0
- package/lib/service.js.map +1 -0
- package/lib/spec.d.ts +33 -0
- package/lib/spec.d.ts.map +1 -0
- package/lib/spec.js +13 -0
- package/lib/spec.js.map +1 -0
- package/lib/validate.d.ts +17 -0
- package/lib/validate.d.ts.map +1 -0
- package/lib/validate.js +35 -0
- package/lib/validate.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","useRef","Modal","IconWarningOutline16","IconChevronRightOutline14","IconEditOutline16","IconLoadingOutline16","IconFolderOpen16","IconFolderClose16","Button","useState","Modal","IconWarningOutline16","IconLoadingOutline16","IconFolderOpen16","Tooltip","IconTrashOutline16","Button","IconSearchOutline16","Input","IconPlusOutline16","useState","useRef","IconFolderOpen16"],"sources":["../src/validate.ts","../src/client/data.ts","../src/client/RefLibBrowser.tsx","../src/client/RefLibPanel.tsx","../src/client/styles.ts","../src/client/RefLibDock.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * 跨 node/client 共享的**零依赖**校验与消毒纯函数。\n *\n * node half(service/logic/render)与 client half(client/data.ts)分属两个构建\n * 产物,无法共享带 node 依赖的模块;本模块只依赖类型(spec.ts,type-only 导入\n * 在打包时被擦除),两半均可直接引用——消除 `isRefLibEntry` 在 logic.ts 与\n * client/data.ts 中的逐字重复,控制字符集合也在此统一维护。\n * @module @hpyperry/dsh-ref-lib/src/validate\n */\n\nimport type { RefLibEntry } from './spec.ts'\n\n/**\n * 不可见控制字符集合(提示词注入卫生):\n * C0(\\u0000-\\u001f)、DEL(\\u007f)、C1(\\u0080-\\u009f)、行/段分隔符\n * (\\u2028/\\u2029——旧实现遗漏,同样能破坏注入格式)。\n * 用于 add 拒绝(service.ts)与渲染层兜底消毒(render.ts)。\n */\nconst CONTROL_CHARACTERS = /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/\nconst CONTROL_CHARACTERS_GLOBAL = /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/g\n\n/** 文本是否含不可见控制字符。 */\nexport function hasControlCharacters(text: string): boolean {\n return CONTROL_CHARACTERS.test(text)\n}\n\n/** 把全部不可见控制字符替换为 U+FFFD(路径/备注最终会进入系统提示词,兜底消毒)。 */\nexport function sanitizeControlCharacters(text: string): string {\n return text.replace(CONTROL_CHARACTERS_GLOBAL, '\\uFFFD')\n}\n\n/** 校验一个未知对象是否为合法条目(宽松:仅要求 id/path 字符串、note 可选字符串)。 */\nexport function isRefLibEntry(value: unknown): value is RefLibEntry {\n if (typeof value !== 'object' || value === null) return false\n const { id, path, note } = value as { id?: unknown; path?: unknown; note?: unknown }\n if (typeof id !== 'string' || typeof path !== 'string') return false\n return note === undefined || typeof note === 'string'\n}\n","/**\n * client 端数据纯函数:/api/ref-lib/* 路由响应解析、错误码解析与路径展示。\n * 独立于 React / runtime,便于单元测试。\n *\n * v5:路由错误响应新增 wire code(ref-lib/missing 等),client 据此把\n * node 端的中文错误消息映射为当前语言的本地化文案(优化点 2 的语言适配)。\n * @module @hpyperry/dsh-ref-lib/src/client/data\n */\n\nimport type { RefLibEntry } from '../spec.ts'\nimport { isRefLibEntry } from '../validate.ts'\n\n/** 路由错误载荷里的附加字段。 */\nexport interface RefLibApiErrorDetails {\n /** 出错的目录路径(missing/not-directory/unsafe)。 */\n readonly path?: string\n /** 未找到的条目 id(unknown-id)。 */\n readonly id?: string\n}\n\n/** 带 wire code 的 API 错误:code 用于本地化文案映射,message 为原始消息兜底。 */\nexport class RefLibApiError extends Error {\n /**\n * @param code - wire 错误码(如 ref-lib/missing)。\n * @param message - 服务端原始错误消息。\n * @param details - 附加字段(path/id)。\n */\n constructor(\n readonly code: string,\n message: string,\n readonly details: RefLibApiErrorDetails = {},\n ) {\n super(message)\n this.name = 'RefLibApiError'\n }\n}\n\n/**\n * 从失败的 /api/ref-lib/* 响应体解析错误码;无 code 的畸形体返回 null。\n * @param value - 非 2xx 响应体(可为任意 JSON)。\n * @returns 错误码 + 原始消息 + 附加字段;无法解析时为 null。\n */\nexport function parseApiErrorPayload(value: unknown): {\n code: string\n message: string\n details: RefLibApiErrorDetails\n} | null {\n if (typeof value !== 'object' || value === null) return null\n const { error, code, path, id } = value as {\n error?: unknown\n code?: unknown\n path?: unknown\n id?: unknown\n }\n if (typeof code !== 'string' || code === '') return null\n const details: RefLibApiErrorDetails = {\n ...(typeof path === 'string' ? { path } : {}),\n ...(typeof id === 'string' ? { id } : {}),\n }\n return { code, message: typeof error === 'string' ? error : code, details }\n}\n\n/**\n * 从 /api/ref-lib/list 路由响应({ libs: [...] })解析参考库列表;\n * 缺失/畸形返回空列表。\n * @param value - 路由响应体。\n * @returns 当前参考库列表。\n */\nexport function parseLibsPayload(value: unknown): RefLibEntry[] {\n if (typeof value !== 'object' || value === null) return []\n const libs = (value as { libs?: unknown }).libs\n if (!Array.isArray(libs)) return []\n return libs.filter(isRefLibEntry)\n}\n\n/**\n * 提取目录路径的展示用基名(列表行主行的可读标题)。\n * 兼容 POSIX 与 Windows 分隔符;去掉尾部分隔符后取最后一段;\n * 根路径(/、C:\\)原样返回。\n * @param path - 规范化绝对路径。\n * @returns 基名(如 /a/b/core → core)。\n */\nexport function libBasename(path: string): string {\n const trimmed = path.replace(/[\\\\/]+$/, '')\n if (trimmed === '') return path\n // Windows 盘符根(C:)不是基名,原样返回带分隔符的根路径(C:\\\\)。\n if (/^[A-Za-z]:$/.test(trimmed)) return path\n const idx = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\\\'))\n return idx === -1 ? trimmed : trimmed.slice(idx + 1)\n}\n","/**\n * 应用内目录浏览器(browse 后端可用时,「选择目录」的落地实现)。\n *\n * 为什么需要它:v6 起 profile 把 directory-picker 从 -auto(本机解析为 native,\n * OS 对话框硬编码英文且每次拉 osascript 进程)切换为 -browse 后,\n * `host.pickDirectory`(native 专用 RPC)不再可用;本组件用 `listDirectory`\n * (Node stdlib 列目录,zh/en 本地化、无 OS 进程)提供等价的目录选择:\n * 面包屑 + 路径编辑 + 子目录列表,点击下行进入,「选择此目录」把当前层级添加为\n * 参考库。简化为单列(与官方 DirectoryBrowser 的 Miller 双列相比),\n * 但覆盖添加参考库的核心路径。\n * @module @hpyperry/dsh-ref-lib/src/client/RefLibBrowser\n */\n\nimport { useEffect, useRef, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport {\n Button,\n IconChevronRightOutline14,\n IconEditOutline16,\n IconFolderClose16,\n IconFolderOpen16,\n IconLoadingOutline16,\n IconWarningOutline16,\n Modal,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { DirectoryEntry, DirectoryListing } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'\n\n/** 面板组件依赖的注入面(由 RefLibDock 提供)。 */\nexport interface RefLibBrowserProps {\n /** 对话框可见性。 */\n open: boolean\n /** 关闭(Escape / 遮罩 / 取消)。 */\n onClose: () => void\n /** 列出一层目录(缺省路径 = 宿主主目录);signal 中止被取代的扫描。 */\n listDirectory: (path?: string, signal?: AbortSignal) => Promise<DirectoryListing>\n /** 本地化取词。 */\n t: TranslateNS<'ref-lib'>\n /** 选定当前层级目录(父组件负责添加并刷新)。 */\n onOpen: (path: string) => void\n}\n\n/** 面包屑展示:主目录子树内以本地化 Home 开头;之外显示完整祖先链。 */\nfunction displayCrumbs(listing: DirectoryListing, homeLabel: string): DirectoryEntry[] {\n const homeIndex = listing.crumbs.findIndex((crumb) => crumb.path === listing.home)\n if (homeIndex === -1) return listing.crumbs\n const tail = listing.crumbs.slice(homeIndex + 1)\n return [{ name: homeLabel, path: listing.home, hidden: false }, ...tail]\n}\n\n/** 平台分隔符(由宿主主目录推断,绝不从键入文本推断)。 */\nfunction separatorOf(listing: DirectoryListing): '\\\\' | '/' {\n return listing.home.includes('\\\\') ? '\\\\' : '/'\n}\n\n/**\n * 渲染应用内目录浏览器。\n * @param props - 见 {@link RefLibBrowserProps}。\n * @returns 对话框元素(关闭时 Modal 返回 null)。\n */\nexport function RefLibBrowser(props: RefLibBrowserProps): ReactElement {\n const { open, onClose, listDirectory, t, onOpen } = props\n const [listing, setListing] = useState<DirectoryListing | null>(null)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n // 路径编辑:null = 面包屑模式;字符串 = 正在键入的草稿。\n const [pathDraft, setPathDraft] = useState<string | null>(null)\n const seq = useRef(0)\n const controller = useRef<AbortController | null>(null)\n // IME 确认(Enter 选中候选)不得提交路径。\n const composing = useRef(false)\n\n /** 新意图胜出:中止在途扫描并使旧结果失效。 */\n const navigate = (target?: string): void => {\n controller.current?.abort()\n controller.current = null\n const mine = ++seq.current\n setLoading(true)\n setError(null)\n const ctrl = new AbortController()\n controller.current = ctrl\n listDirectory(target, ctrl.signal).then(\n (next) => {\n if (mine !== seq.current) return\n setListing(next)\n setLoading(false)\n },\n (cause: unknown) => {\n if (mine !== seq.current) return\n setLoading(false)\n setError(cause instanceof Error ? cause.message : String(cause))\n },\n )\n }\n\n // 打开时列主目录;关闭时清理在途扫描。\n useEffect(() => {\n if (open) {\n navigate()\n } else {\n seq.current += 1\n controller.current?.abort()\n controller.current = null\n setPathDraft(null)\n setError(null)\n }\n }, [open])\n\n const openPathEditor = (): void => {\n if (listing === null) {\n setPathDraft('')\n return\n }\n const sep = separatorOf(listing)\n const base = listing.path\n setPathDraft(base.endsWith(sep) ? base : base + sep)\n }\n\n const commitPath = (): void => {\n const draft = pathDraft ?? ''\n if (draft.trim() === '') return\n navigate(draft)\n setPathDraft(null)\n }\n\n const crumbs = listing === null ? [] : displayCrumbs(listing, t('browser.home'))\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={t('browser.title')}\n closeLabel={t('browser.cancel')}\n className=\"reflib-browser\"\n >\n <div className=\"reflib-panel\">\n {error !== null && (\n <div className=\"reflib-error\" role=\"alert\">\n <IconWarningOutline16 size={14} className=\"reflib-errorIcon\" />\n <span>{error}</span>\n </div>\n )}\n {/* 路径条:面包屑 + 编辑路径入口 */}\n <div className=\"reflib-browserPath\">\n {pathDraft === null ? (\n <>\n <span className=\"reflib-browserCrumbs\" role=\"navigation\">\n {crumbs.map((crumb, index) => (\n <span key={crumb.path} className=\"reflib-browserCrumb\">\n {index > 0 && <IconChevronRightOutline14 size={12} className=\"reflib-browserCrumbChevron\" />}\n <button\n type=\"button\"\n className=\"reflib-browserCrumbBtn\"\n disabled={loading}\n onClick={() => {\n navigate(crumb.path)\n }}\n >\n {crumb.name}\n </button>\n </span>\n ))}\n </span>\n <button\n type=\"button\"\n className=\"reflib-browserEdit\"\n aria-label={t('browser.editPath')}\n title={t('browser.editPath')}\n disabled={loading}\n onClick={openPathEditor}\n >\n <IconEditOutline16 size={14} />\n </button>\n </>\n ) : (\n <input\n className=\"reflib-browserInput\"\n value={pathDraft}\n aria-label={t('browser.editPath')}\n placeholder={t('browser.pathPlaceholder')}\n autoFocus\n disabled={loading}\n onChange={(event) => {\n setPathDraft(event.currentTarget.value)\n }}\n onCompositionStart={() => {\n composing.current = true\n }}\n onCompositionEnd={() => {\n composing.current = false\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !composing.current) {\n event.preventDefault()\n commitPath()\n }\n if (event.key === 'Escape') {\n event.stopPropagation()\n setPathDraft(null)\n }\n }}\n />\n )}\n </div>\n {/* 目录列表 */}\n {loading ? (\n <div className=\"reflib-status\" role=\"status\">\n <IconLoadingOutline16 size={20} className=\"reflib-spin reflib-statusIcon\" />\n <span className=\"reflib-statusText\">{t('browser.loading')}</span>\n </div>\n ) : listing !== null && listing.entries.length === 0 ? (\n <div className=\"reflib-status\">\n <IconFolderOpen16 size={22} className=\"reflib-statusIcon\" />\n <span className=\"reflib-statusText\">{t('browser.empty')}</span>\n </div>\n ) : (\n listing !== null && (\n <div className=\"reflib-browserList\">\n {listing.entries.map((entry) => (\n <button\n key={entry.path}\n type=\"button\"\n className=\"reflib-browserRow\"\n aria-label={t('browser.enter.aria', { name: entry.name })}\n disabled={loading}\n onClick={() => {\n navigate(entry.path)\n }}\n >\n <IconFolderClose16 size={16} className=\"reflib-browserRowIcon\" />\n <span className=\"reflib-browserRowName\">{entry.name}</span>\n </button>\n ))}\n </div>\n )\n )}\n <div className=\"reflib-divider\" />\n {/* 底部操作 */}\n <div className=\"reflib-browserFooter\">\n <Button variant=\"outline\" size=\"sm\" disabled={loading} onClick={onClose}>\n {t('browser.cancel')}\n </Button>\n <Button\n variant=\"primary\"\n size=\"sm\"\n disabled={loading || listing === null}\n aria-label={t('browser.open.aria')}\n onClick={() => {\n if (listing !== null) onOpen(listing.path)\n }}\n >\n {t('browser.open')}\n </Button>\n </div>\n </div>\n </Modal>\n )\n}\n","/**\n * 参考库管理面板(Modal):列表(基名 + 全路径、移除操作)+ 添加表单。\n *\n * 设计(优化点 1/2):全部样式走 “--dsw-*” 设计令牌(随浅/深主题适配),\n * 列表行以「基名为主行、完整路径为次行」呈现,移除为带危险 hover 的图标按钮,\n * 添加为主按钮 + 路径输入(支持直接粘贴路径,绕开卡顿的系统选择器)+ 系统选择器\n * 兜底;空态/加载态/错误态各有独立视觉。文案全部经 `t` 本地化(zh/en)。\n * 本组件为纯展示:异步操作与错误归集由 RefLibDock 持有。\n * @module @hpyperry/dsh-ref-lib/src/client/RefLibPanel\n */\n\nimport { useEffect, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport {\n Button,\n IconFolderOpen16,\n IconLoadingOutline16,\n IconPlusOutline16,\n IconSearchOutline16,\n IconTrashOutline16,\n IconWarningOutline16,\n Input,\n Modal,\n Tooltip,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { RefLibEntry } from '../spec.ts'\nimport { libBasename } from './data.ts'\n\n/** 面板组件依赖的注入面(由 RefLibDock 提供)。 */\nexport interface RefLibPanelProps {\n /** 对话框可见性。 */\n open: boolean\n /** 关闭(Escape / 遮罩 / 关闭按钮)。 */\n onClose: () => void\n /** 当前参考库列表。 */\n libs: readonly RefLibEntry[]\n /** 初次加载中(列表区显示加载态)。 */\n loading: boolean\n /** 任一异步操作(添加/移除)进行中:禁用全部操作。 */\n busy: boolean\n /** 系统目录选择器已打开(禁用添加表单,防重复唤起)。 */\n picking: boolean\n /** 正在移除的条目 id(该行显示行内 spinner)。 */\n removingId: string | null\n /** 已本地化的错误文案(null 表示无错误)。 */\n error: string | null\n /** 本地化取词。 */\n t: TranslateNS<'ref-lib'>\n /** 移除一个条目(父组件执行异步并刷新)。 */\n onRemove: (id: string) => void\n /** 按输入路径添加;失败时抛出(面板保留输入内容)。 */\n onAddPath: (path: string) => Promise<void>\n /** 选择目录(按宿主能力自适应:browse 应用内浏览器 / native 系统选择器;父组件确认后添加)。 */\n onBrowse: () => Promise<void>\n}\n\n/**\n * 渲染参考库管理面板。\n * @param props - 见 {@link RefLibPanelProps}。\n * @returns 对话框元素(关闭时 Modal 返回 null)。\n */\nexport function RefLibPanel(props: RefLibPanelProps): ReactElement {\n const { open, onClose, libs, loading, busy, picking, removingId, error, t, onRemove, onAddPath, onBrowse } = props\n const [draft, setDraft] = useState('')\n\n // 面板关闭时清空输入草稿:每次打开都是干净的添加表单(失败保留输入的行为只限于面板打开期间)。\n useEffect(() => {\n if (!open) setDraft('')\n }, [open])\n\n const handleSubmit = async (): Promise<void> => {\n const trimmed = draft.trim()\n if (trimmed === '') return\n try {\n await onAddPath(trimmed)\n setDraft('')\n } catch {\n /* 错误由父组件在统一错误槽展示;输入保留以便修正 */\n }\n }\n\n return (\n <Modal\n open={open}\n onClose={onClose}\n title={t('panel.title')}\n closeLabel={t('panel.close')}\n description={t('panel.description', { count: String(libs.length) })}\n >\n <div className=\"reflib-panel\">\n {error !== null && (\n <div className=\"reflib-error\" role=\"alert\">\n <IconWarningOutline16 size={14} className=\"reflib-errorIcon\" />\n <span>{error}</span>\n </div>\n )}\n {loading ? (\n <div className=\"reflib-status\" role=\"status\">\n <IconLoadingOutline16 size={20} className=\"reflib-spin reflib-statusIcon\" />\n <span className=\"reflib-statusText\">{t('list.loading')}</span>\n </div>\n ) : libs.length === 0 ? (\n <div className=\"reflib-status\">\n <IconFolderOpen16 size={22} className=\"reflib-statusIcon\" />\n <span className=\"reflib-statusText\">{t('list.empty')}</span>\n <span className=\"reflib-statusHint\">{t('list.empty.hint')}</span>\n </div>\n ) : (\n <div className=\"reflib-list\">\n {libs.map((entry) => {\n const name = libBasename(entry.path)\n const removing = removingId === entry.id\n return (\n <div key={entry.id} className=\"reflib-row\" data-removing={removing || undefined}>\n <IconFolderOpen16 size={16} className=\"reflib-rowIcon\" />\n <div className=\"reflib-rowBody\">\n <span className=\"reflib-rowName\" title={entry.path}>\n {name}\n </span>\n <span className=\"reflib-rowPath\" title={entry.path}>\n {entry.path}\n </span>\n </div>\n <Tooltip label={t('list.remove')} side=\"top\" delayMs={400}>\n <button\n type=\"button\"\n className=\"reflib-rowRemove\"\n aria-label={t('list.remove.aria', { name })}\n disabled={busy || removingId !== null}\n onClick={() => {\n onRemove(entry.id)\n }}\n >\n {removing ? (\n <IconLoadingOutline16 size={14} className=\"reflib-spin\" />\n ) : (\n <IconTrashOutline16 size={14} />\n )}\n </button>\n </Tooltip>\n </div>\n )\n })}\n </div>\n )}\n <div className=\"reflib-divider\" />\n <div className=\"reflib-add\">\n <span className=\"reflib-addLabel\">{t('add.label')}</span>\n {/* 主方式:选择目录(browse 应用内浏览器 / native 系统选择器,全宽主按钮,优先级最高) */}\n <Button\n variant=\"primary\"\n className=\"reflib-addBrowseMain\"\n icon={<IconSearchOutline16 size={16} />}\n disabled={busy || picking}\n onClick={() => {\n void onBrowse()\n }}\n >\n {t('add.browse')}\n </Button>\n {/* 备选:手动输入路径(输入 + 添加 一组) */}\n <div className=\"reflib-addManual\">\n <span className=\"reflib-addManualLabel\">{t('add.manual')}</span>\n <div className=\"reflib-addRow\">\n <Input\n className=\"reflib-addInputWrap\"\n value={draft}\n placeholder={t('add.placeholder')}\n aria-label={t('add.label')}\n disabled={busy || picking}\n onChange={(event) => {\n setDraft(event.currentTarget.value)\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.nativeEvent.isComposing) {\n event.preventDefault()\n void handleSubmit()\n }\n }}\n />\n <Button\n variant=\"primary\"\n size=\"sm\"\n icon={<IconPlusOutline16 size={14} />}\n disabled={busy || picking || draft.trim() === ''}\n onClick={() => {\n void handleSubmit()\n }}\n >\n {t('add.submit')}\n </Button>\n </div>\n </div>\n <span className=\"reflib-addHint\">{t('add.hint')}</span>\n </div>\n </div>\n </Modal>\n )\n}\n","/**\n * ref-lib client 样式注入:在文档头挂一张本插件专属的 <style data-plugin> 标签。\n *\n * 为什么不用 CSS Modules:ref-lib 的 tsdown 配置未移植 core 的 lightningcss\n * CSS-Modules 插件(避免新增 native 依赖与构建面),而内联 style 无法表达\n * hover/媒体查询/动画。因此采用运行时注入单张样式表——与 loader 的清理契约\n * 兼容(`data-plugin` 标签在插件卸载/HMR 时由 loader 移除,重新物化时本模块\n * 重新注入,幂等)。类名统一 `reflib-` 前缀防碰撞;颜色/字号/圆角全部走\n * `--dsw-*` 设计令牌,随主题(浅色/深色)自动适配——这是\"字体显示不一致、\n * 没有设计感\"的根因修复之一(旧实现硬编码 #d33 等,不随主题变化)。\n * @module @hpyperry/dsh-ref-lib/src/client/styles\n */\n\n/** 插件 id(与 package.json 的 bundle 手递一致,loader 按它清理样式)。 */\nconst PLUGIN_ID = '@hpyperry/dsh-ref-lib'\n/** 样式标签稳定 id(幂等注入的判据)。 */\nconst STYLE_TAG_ID = '@hpyperry/dsh-ref-lib/styles'\n\nconst CSS = `\n/* ── 输入卡正上方的入口胶囊行(dock)──\n 独立一行,横跨整个 composer 列;chip 与输入卡左缘对齐——用官方设计令牌\n (--dsh-composer-side-clearance / --dsh-composer-card-max-width)纯 CSS 计算,\n 零 JS 测量(v7 起取消 hero 相位测量/绝对定位);hero 与 active 一致,\n plan/model 等工具行座位的启停不影响本行位置。 */\n.reflib-dock {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n min-width: 0;\n /* 镜像输入卡居中:chip 落在输入卡左缘之上(宽视口 = 居中余量,窄视口 = clearance) */\n padding-left: max(\n var(--dsh-composer-side-clearance, 16px),\n calc((100% - var(--dsh-composer-card-max-width, 100%)) / 2)\n );\n}\n\n.reflib-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n height: 30px;\n padding: 0 10px 0 8px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 15px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 120ms ease, border-color 120ms ease;\n}\n\n.reflib-chip:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.reflib-chip:active {\n background: var(--dsw-alias-interactive-bg-active);\n}\n\n.reflib-chip[data-active='true'] {\n background: var(--dsw-alias-interactive-bg-hover);\n border-color: var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n}\n\n.reflib-chipIcon {\n display: inline-flex;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.reflib-chipLabel {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.reflib-chipBadge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 9px;\n background: var(--dsw-alias-state-business-tertiary);\n color: var(--dsw-alias-label-primary-bluish);\n font-size: 11px;\n line-height: 18px;\n font-weight: 500;\n}\n\n/* ── 应用内目录浏览器(browse 后端)── */\n.reflib-browser {\n width: min(460px, 100%);\n}\n\n.reflib-browserPath {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n height: 32px;\n padding: 0 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.reflib-browserCrumbs {\n display: flex;\n flex: 1;\n align-items: center;\n min-width: 0;\n overflow-x: auto;\n white-space: nowrap;\n scrollbar-width: none;\n}\n\n.reflib-browserCrumbs::-webkit-scrollbar {\n display: none;\n}\n\n.reflib-browserCrumb {\n display: inline-flex;\n align-items: center;\n flex: none;\n}\n\n.reflib-browserCrumbChevron {\n flex: none;\n color: var(--dsw-alias-label-caption);\n}\n\n.reflib-browserCrumbBtn {\n display: inline-flex;\n align-items: center;\n max-width: 160px;\n padding: 2px 6px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 18px;\n overflow: hidden;\n text-overflow: ellipsis;\n cursor: pointer;\n}\n\n.reflib-browserCrumbBtn:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.reflib-browserCrumbBtn:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.reflib-browserEdit {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 24px;\n height: 24px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n\n.reflib-browserEdit:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.reflib-browserEdit:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.reflib-browserInput {\n flex: 1;\n min-width: 0;\n border: none;\n outline: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.reflib-browserInput::placeholder {\n color: var(--dsw-alias-label-dimmed);\n}\n\n.reflib-browserList {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 264px;\n min-height: 60px;\n overflow-y: auto;\n margin: 0 -8px;\n padding: 0 8px;\n}\n\n.reflib-browserRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n padding: 7px 8px;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n text-align: left;\n}\n\n.reflib-browserRow:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.reflib-browserRow:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.reflib-browserRowIcon {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.reflib-browserRowName {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.reflib-browserFooter {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n min-width: 0;\n}\n\n/* ── 管理面板 ── */\n.reflib-panel {\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n\n/* 列表:固定最大高度 + 内滚动(旧实现整个面板滚动、无设计,此处分层) */\n.reflib-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 264px;\n min-height: 0;\n overflow-y: auto;\n margin: 0 -8px;\n padding: 0 8px;\n}\n\n.reflib-row {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n padding: 7px 8px;\n border-radius: 10px;\n background: transparent;\n transition: background-color 120ms ease;\n}\n\n.reflib-row:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.reflib-row[data-removing='true'] {\n opacity: 0.55;\n pointer-events: none;\n}\n\n.reflib-rowIcon {\n display: inline-flex;\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.reflib-rowBody {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 1px;\n min-width: 0;\n}\n\n.reflib-rowName {\n overflow: hidden;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n line-height: 18px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.reflib-rowPath {\n overflow: hidden;\n color: var(--dsw-alias-label-tertiary);\n font-family: var(--ds-font-family-code);\n font-size: 11px;\n line-height: 16px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.reflib-rowRemove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 28px;\n height: 28px;\n padding: 0;\n border: none;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n transition: background-color 120ms ease, color 120ms ease;\n}\n\n.reflib-rowRemove:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n}\n\n.reflib-rowRemove:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n/* 空态 / 加载态 / 错误态 */\n.reflib-status {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 22px 8px;\n color: var(--dsw-alias-label-caption);\n text-align: center;\n}\n\n.reflib-statusIcon {\n display: inline-flex;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.reflib-statusText {\n font-size: 13px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n.reflib-statusHint {\n max-width: 260px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-caption);\n}\n\n.reflib-error {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 6px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover-danger);\n color: var(--dsw-alias-state-error-primary);\n font-size: 12px;\n line-height: 18px;\n word-break: break-word;\n}\n\n.reflib-errorIcon {\n display: inline-flex;\n flex: none;\n margin-top: 1px;\n}\n\n.reflib-spin {\n animation: reflib-spin 800ms linear infinite;\n}\n\n@keyframes reflib-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.reflib-divider {\n height: 1px;\n margin: 2px 0;\n background: var(--dsw-alias-border-l2);\n}\n\n/* ── 添加表单 ──\n 两组操作分离(优化点 2.1):手动输入路径 + 「添加」是一组(主路径);\n 「选择目录」是另一组备选方式,独占一行,避免并排造成困惑。 */\n.reflib-add {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n\n.reflib-addLabel {\n font-size: 12px;\n line-height: 16px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* 主方式:选择目录 —— 全宽主按钮(优先级最高) */\n.reflib-addBrowseMain {\n width: 100%;\n}\n\n/* 备选:手动输入路径组 */\n.reflib-addManual {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n\n.reflib-addManualLabel {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-caption);\n}\n\n.reflib-addRow {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.reflib-addInputWrap {\n flex: 1 1 180px;\n min-width: 0;\n}\n\n/* 输入字体与面板一致(13px,同列表行名),placeholder 不再比整体大;\n 文案已缩短保证完整显示 */\n.reflib-addInputWrap > input {\n width: 100%;\n box-sizing: border-box;\n font-size: 13px;\n line-height: 18px;\n}\n\n.reflib-addHint {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-caption);\n}\n\n/* 窄视口:主路径行换行,输入框占满一行 */\n@media (max-width: 440px) {\n .reflib-addRow {\n flex-wrap: wrap;\n }\n\n .reflib-addInputWrap {\n flex: 1 1 100%;\n }\n\n .reflib-addRow > button:last-child {\n margin-left: auto;\n }\n}\n`\n\n/** 注入/确保样式表存在(幂等;HMR 重执行时旧标签已被 loader 移除,重新注入)。 */\nexport function ensureRefLibStyles(): void {\n if (typeof document === 'undefined') return\n if (document.querySelector('style[data-plugin-css=\"' + STYLE_TAG_ID + '\"]') !== null) return\n const tag = document.createElement('style')\n tag.dataset.plugin = PLUGIN_ID\n tag.dataset.pluginCss = STYLE_TAG_ID\n tag.textContent = CSS\n document.head.appendChild(tag)\n}\n","/**\n * 参考库入口(conversation.input.dock,输入卡正上方)+ 面板状态持有者。\n *\n * 位置(v7):v5/v6 曾在 hero 相位把胶囊**测量定位**提入官方 hero 行(紧跟\"标准\n * 模式\"chip 之后)。该像素测量与 hero 行的异步内容存在竞态——模式 chip 的 roster\n * 经 RPC 异步挂载、Web 字体异步加载、其他插件 chip 异步加入——而重测触发面\n * (hero 行/槽位出口/栈均为固定盒或 display:contents)对\"行内内容变化\"是盲区,\n * 导致偶发与模式按钮重叠。v7 起**取消测量与绝对定位**:胶囊统一渲染在 dock 槽位\n * (输入卡正上方)的独立一行,左缘经官方设计令牌(--dsh-composer-side-clearance /\n * --dsh-composer-card-max-width)纯 CSS 与输入卡左缘对齐——hero/active 一致、\n * 零 JS 测量、零竞态;plan/model 等工具行座位的启停不影响本行位置;与其他 dock\n * 条带(todo/goal/queue)按 order 流式堆叠,天然不重叠。\n *\n * 数据(v4/v5):读/写经 /api/ref-lib/* 路由(静默、无命令卡片);\n * 目录选择走系统原生选择器,另提供路径输入直加(绕开卡顿的原生对话框)。\n * @module @hpyperry/dsh-ref-lib/src/client/RefLibDock\n */\n\nimport { useEffect, useRef, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { DirectoryListing, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\nimport { IconFolderOpen16 } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { RefLibEntry } from '../spec.ts'\nimport { RefLibApiError } from './data.ts'\nimport type { RefLibKey } from './locales.ts'\nimport { RefLibBrowser } from './RefLibBrowser.tsx'\nimport { RefLibPanel } from './RefLibPanel.tsx'\nimport { ensureRefLibStyles } from './styles.ts'\n\n/** 面板组件依赖的注入面(apply 闭包绑定 runtime 能力)。 */\nexport interface RefLibDockInjected {\n /** 读取会话的参考库列表(GET /api/ref-lib/list,静默)。 */\n load: (sessionId: SessionId) => Promise<RefLibEntry[]>\n /** 添加一个参考库目录(POST /api/ref-lib/add,静默)。 */\n add: (sessionId: SessionId, path: string) => Promise<void>\n /** 移除一个参考库条目(POST /api/ref-lib/remove,静默)。 */\n remove: (sessionId: SessionId, id: string) => Promise<void>\n /** 唤起系统原生\"选择文件夹\"对话框;取消时返回 null。 */\n pickDirectory: () => Promise<string | null>\n /** 列出一层目录(browse 后端);缺省路径 = 宿主主目录;signal 中止扫描。 */\n listDirectory: (path?: string, signal?: AbortSignal) => Promise<DirectoryListing>\n}\n\n/** 完整 props:input.dock 运行时套件 + 注入面 + 本地化 seat。 */\nexport type RefLibDockProps = PropsRuntime<'conversation.input.dock'> & RefLibDockInjected & PropsLocale<'ref-lib'>\n\n/** wire 错误码 → 本地化文案键(未知码回退原始消息)。 */\nconst ERROR_KEYS: Record<string, RefLibKey> = {\n 'ref-lib/missing': 'error.missing',\n 'ref-lib/not-directory': 'error.notDirectory',\n 'ref-lib/unsafe': 'error.unsafe',\n 'ref-lib/unknown-id': 'error.unknownId',\n}\n\n/** 把未知错误规整为可展示文案。 */\nfunction messageOf(cause: unknown): string {\n return cause instanceof Error ? cause.message : String(cause)\n}\n\n/**\n * 把 API 错误本地化:已知 wire code 映射为当前语言文案(带 path/id 参数),\n * 其余错误原样展示服务端消息。\n */\nfunction formatError(cause: unknown, t: RefLibDockProps['t']): string {\n if (cause instanceof RefLibApiError) {\n const key = ERROR_KEYS[cause.code]\n if (key !== undefined) {\n const params =\n cause.details.path !== undefined\n ? { path: cause.details.path }\n : cause.details.id !== undefined\n ? { id: cause.details.id }\n : undefined\n return t(key, params)\n }\n }\n return messageOf(cause)\n}\n\n// 模块装载即注入样式(幂等);组件挂载后再兜底一次(覆盖装载早于 DOM 的情况)。\nensureRefLibStyles()\n\n/**\n * 输入卡正上方的参考库入口:胶囊按钮(图标 + 文案 + 数量徽标)唤起管理面板。\n * 位置由 .reflib-dock 的纯 CSS padding 与输入卡左缘对齐,无任何 JS 测量。\n * @param props - sessionId 与注入面。\n * @returns 胶囊入口(+ 打开时的管理面板)。\n */\nexport function RefLibDock(props: RefLibDockProps): ReactElement {\n const { sessionId, load, add, remove, pickDirectory, listDirectory, t } = props\n const [open, setOpen] = useState(false)\n const [libs, setLibs] = useState<RefLibEntry[]>([])\n const [loading, setLoading] = useState(false)\n const [busy, setBusy] = useState(false)\n const [picking, setPicking] = useState(false)\n const [removingId, setRemovingId] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n // 并发读守卫:仅采纳最新一次 refresh 的结果(挂载预载与开面板刷新可能重叠)。\n const seq = useRef(0)\n\n // 目录选择能力(v6):browse(应用内浏览器,listDirectory)或 native(OS 对话框,\n // pickDirectory)。探测一次并缓存;host 侧能力在单次 boot 内稳定。\n const [pickerMode, setPickerMode] = useState<'browse' | 'native' | null>(null)\n const [browserOpen, setBrowserOpen] = useState(false)\n const detectPicker = async (): Promise<'browse' | 'native'> => {\n if (pickerMode !== null) return pickerMode\n let mode: 'browse' | 'native'\n try {\n await listDirectory(undefined, new AbortController().signal)\n mode = 'browse'\n } catch {\n // browse 不可用(directory-picker-unavailable)→ 回退 native 尝试;\n // pickDirectory 若也不可用,由错误槽如实展示。\n mode = 'native'\n }\n setPickerMode(mode)\n return mode\n }\n\n const refresh = async (): Promise<void> => {\n const mine = ++seq.current\n try {\n const next = await load(sessionId)\n if (mine !== seq.current) return\n setLibs(next)\n setError(null)\n } catch (cause) {\n if (mine !== seq.current) return\n setError(formatError(cause, t))\n } finally {\n if (mine === seq.current) setLoading(false)\n }\n }\n\n // 挂载预载。依赖 sessionId:若同一 dock 实例跨会话复用(槽位按会话切换),\n // 会话切换后徽标/列表随之刷新,避免停留在上一个会话的过期数据(seq 守卫防竞态)。\n useEffect(() => {\n ensureRefLibStyles()\n void refresh()\n }, [sessionId])\n useEffect(() => {\n if (open) {\n setLoading(true)\n void refresh()\n }\n }, [open])\n\n const handleRemove = async (id: string): Promise<void> => {\n setRemovingId(id)\n setBusy(true)\n setError(null)\n try {\n await remove(sessionId, id)\n await refresh()\n } catch (cause) {\n setError(formatError(cause, t))\n } finally {\n setRemovingId(null)\n setBusy(false)\n }\n }\n\n const handleAddPath = async (path: string): Promise<void> => {\n setBusy(true)\n setError(null)\n try {\n await add(sessionId, path)\n await refresh()\n } catch (cause) {\n setError(formatError(cause, t))\n // 失败时重新抛出:让面板保留输入内容供修正\n throw cause\n } finally {\n setBusy(false)\n }\n }\n\n /** 添加一个已选定目录并刷新列表;返回是否成功(失败时错误已入错误槽,不抛出)。 */\n const addPath = async (path: string): Promise<boolean> => {\n setBusy(true)\n setError(null)\n try {\n await add(sessionId, path)\n await refresh()\n return true\n } catch (cause) {\n setError(formatError(cause, t))\n return false\n } finally {\n setBusy(false)\n }\n }\n\n const handleBrowse = async (): Promise<void> => {\n setError(null)\n // 探测目录选择能力:browse → 应用内目录浏览器;native → 系统 OS 对话框。\n let mode: 'browse' | 'native'\n try {\n mode = await detectPicker()\n } catch (cause) {\n setError(formatError(cause, t))\n return\n }\n if (mode === 'browse') {\n setBrowserOpen(true)\n return\n }\n setPicking(true)\n try {\n const picked = await pickDirectory()\n if (picked !== null) await addPath(picked)\n } catch (cause) {\n setError(formatError(cause, t))\n } finally {\n setPicking(false)\n }\n }\n\n /** 应用内浏览器选定目录 → 添加成功才关闭(失败时错误显示在面板,浏览器保持打开便于重试)。 */\n const handleBrowserPick = async (path: string): Promise<void> => {\n if (await addPath(path)) setBrowserOpen(false)\n }\n\n return (\n <>\n <div className=\"reflib-dock\">\n <button\n type=\"button\"\n className=\"reflib-chip\"\n data-active={open || undefined}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-label={libs.length > 0 ? t('dock.count.aria', { count: String(libs.length) }) : t('dock.aria')}\n title={t('dock.aria')}\n onClick={() => {\n setOpen((value) => !value)\n }}\n >\n <span className=\"reflib-chipIcon\">\n <IconFolderOpen16 size={14} />\n </span>\n <span className=\"reflib-chipLabel\">{t('dock.label')}</span>\n {libs.length > 0 && <span className=\"reflib-chipBadge\">{libs.length}</span>}\n </button>\n </div>\n <RefLibPanel\n open={open}\n onClose={() => {\n setOpen(false)\n }}\n libs={libs}\n loading={loading}\n busy={busy}\n picking={picking}\n removingId={removingId}\n error={error}\n t={t}\n onRemove={(id) => {\n void handleRemove(id)\n }}\n onAddPath={handleAddPath}\n onBrowse={handleBrowse}\n />\n <RefLibBrowser\n open={browserOpen}\n onClose={() => {\n setBrowserOpen(false)\n }}\n listDirectory={listDirectory}\n t={t}\n onOpen={(path) => {\n void handleBrowserPick(path)\n }}\n />\n </>\n )\n}\n","/**\n * ref-lib client 本地化字典(zh 为键集唯一来源,en 与之逐键对齐)。\n *\n * 语言适配(优化点 2):面板、入口胶囊与添加表单的全部文案经\n * `ctx.locale.register(NS, { zh, en })` 注册,槽位注册声明 `locale: NS`,\n * 组件经框架注入的 `t` seat 取词(活动语言实时切换,缺省回退 zh)。\n * 键对等由 `satisfies Record<RefLibKey, string>` 在编译期强制。\n * @module @hpyperry/dsh-ref-lib/src/client/locales\n */\n\n/** 简体中文字典(键集唯一来源)。 */\nexport const zh = {\n // ── 输入框上方入口胶囊(conversation.input.dock)──\n 'dock.label': '参考库',\n 'dock.aria': '管理只读参考库',\n 'dock.count.aria': '参考库,共 {count} 个',\n // ── 管理面板 ──\n 'panel.title': '参考库',\n 'panel.close': '关闭',\n 'panel.description': '共 {count} 个只读参考库:供 agent 读取参考,禁止修改其中文件',\n 'list.remove': '移除',\n 'list.remove.aria': '移除参考库 {name}',\n 'list.empty': '还没有参考库',\n 'list.empty.hint': '在下方输入目录路径,或点击「选择目录」从系统选择',\n 'list.loading': '正在加载…',\n // ── 添加表单 ──\n 'add.label': '添加参考库',\n 'add.placeholder': '输入目录路径(支持 ~ 与相对)',\n 'add.submit': '添加',\n 'add.manual': '或手动输入路径',\n 'add.browse': '选择目录',\n 'add.hint': '目录仅作只读参考;位于工作区之外时由系统强制只读',\n // ── 应用内目录浏览器(browse 后端)──\n 'browser.title': '选择目录',\n 'browser.home': '主目录',\n 'browser.open': '选择此目录',\n 'browser.open.aria': '添加当前目录为参考库',\n 'browser.enter.aria': '进入目录 {name}',\n 'browser.editPath': '编辑路径',\n 'browser.pathPlaceholder': '输入绝对路径,回车跳转',\n 'browser.empty': '该目录下没有子目录',\n 'browser.loading': '加载中…',\n 'browser.cancel': '取消',\n // ── 错误映射(wire code → 本地化文案)──\n 'error.missing': '目录不存在:{path}',\n 'error.notDirectory': '不是目录:{path}',\n 'error.unsafe': '路径包含控制字符,已拒绝:{path}',\n 'error.unknownId': '未找到参考库条目:{id}',\n} satisfies Record<string, string>\n\n/** ref-lib 命名空间的键集。 */\nexport type RefLibKey = keyof typeof zh\n\n/** 英文词典,与 zh 键集逐键对齐(编译期校验)。 */\nexport const en = {\n 'dock.label': 'Reference Library',\n 'dock.aria': 'Manage read-only reference libraries',\n 'dock.count.aria': 'Reference library, {count} total',\n 'panel.title': 'Reference Library',\n 'panel.close': 'Close',\n 'panel.description':\n '{count} read-only reference libraries: the agent may read them for reference, but must not modify any files',\n 'list.remove': 'Remove',\n 'list.remove.aria': 'Remove reference library {name}',\n 'list.empty': 'No reference libraries yet',\n 'list.empty.hint': 'Enter a directory path below, or click “Choose directory” to pick one',\n 'list.loading': 'Loading…',\n 'add.label': 'Add a reference library',\n 'add.placeholder': 'Enter a directory path (~ or relative)',\n 'add.submit': 'Add',\n 'add.manual': 'or enter a path manually',\n 'add.browse': 'Choose directory',\n 'add.hint': 'Directories are read-only references; outside the workspace they are enforced read-only by the system',\n // ── In-app directory browser (browse backend) ──\n 'browser.title': 'Select Directory',\n 'browser.home': 'Home',\n 'browser.open': 'Select this folder',\n 'browser.open.aria': 'Add the current directory as a reference library',\n 'browser.enter.aria': 'Open folder {name}',\n 'browser.editPath': 'Edit path',\n 'browser.pathPlaceholder': 'Type an absolute path and press Enter',\n 'browser.empty': 'No subdirectories here',\n 'browser.loading': 'Loading…',\n 'browser.cancel': 'Cancel',\n 'error.missing': 'Directory does not exist: {path}',\n 'error.notDirectory': 'Not a directory: {path}',\n 'error.unsafe': 'Path contains control characters and was rejected: {path}',\n 'error.unknownId': 'Reference library entry not found: {id}',\n} satisfies Record<RefLibKey, string>\n","/**\n * ref-lib client 插件(浏览器端):在输入框上方(conversation.input.dock)\n * 注册「参考库」入口,提供 per-session 的 UI 交互式管理。\n *\n * 入口位置(v7):注册在输入框上方(conversation.input.dock)槽位,胶囊渲染在\n * 输入卡正上方的独立一行,经 CSS 令牌与输入卡左缘对齐——hero(新会话)与 active\n * (具体对话)统一可见、零测量、零竞态,新建会话一开始即可注入参考库。\n *\n * 数据通道(v4/v5):client 经**普通同源 fetch** 访问插件在宿主 `ctx.webServer`\n * 上自注册的 /api/ref-lib/* HTTP 路由(node 端读写 sidecar)——静默双向、\n * 不渲染命令卡片、不执行命令、不产生用户消息。路由 loopback-only 护栏见\n * `src/routes.ts`。目录选择走 `ctx.workspaces.pickDirectory()`(host 原生 OS\n * 选择器),另提供路径输入直加(优化点 2:绕开卡顿的原生对话框)。\n * 文案经 `ctx.locale` 全量本地化(zh/en,优化点 2)。\n * @module @hpyperry/dsh-ref-lib/src/client\n */\n\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\n// Side-effect type imports:加载 slot 声明合并(conversation.input.dock 由\n// ui-conversation 声明)与 locale 服务的类型。\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type { RefLibEntry } from '../spec.ts'\nimport { parseApiErrorPayload, parseLibsPayload, RefLibApiError } from './data.ts'\nimport { RefLibDock, type RefLibDockInjected } from './RefLibDock.tsx'\nimport { zh, en, type RefLibKey } from './locales.ts'\n\n/** 所需服务(cordis fiber inject)。 */\nexport const inject = ['slots', 'workspaces', 'locale']\n\n/** 本插件文案命名空间。 */\nconst NS = 'ref-lib'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** ref-lib 面板/入口文案。 */\n 'ref-lib': RefLibKey\n }\n}\n\n/**\n * 一条对宿主 /api/ref-lib 路由族的 JSON 调用;失败时优先抛出带 wire code 的\n * RefLibApiError(用于本地化),无 code 时回退普通 Error。\n */\nasync function api<T>(path: string, payload?: unknown, method = 'POST'): Promise<T> {\n const res = await fetch(\n '/api/ref-lib' + path,\n payload === undefined\n ? undefined\n : {\n method,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n )\n let body: unknown = null\n try {\n body = await res.json()\n } catch {\n /* non-JSON error body */\n }\n if (!res.ok) {\n const parsed = parseApiErrorPayload(body)\n if (parsed !== null) throw new RefLibApiError(parsed.code, parsed.message, parsed.details)\n const message = (body as { error?: unknown } | null)?.error ?? 'HTTP ' + res.status\n throw new Error(typeof message === 'string' ? message : String(message))\n }\n return body as T\n}\n\n/**\n * 注册输入框上方的「参考库」入口与管理面板。\n * @param ctx - client 根上下文。\n */\nexport function apply(ctx: ClientContext): void {\n // 注册本插件文案命名空间(zh/en 逐键对齐,编译期校验)。\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ref-lib: dictionaries')\n\n const injected = (): RefLibDockInjected => ({\n load: async (sessionId: SessionId): Promise<RefLibEntry[]> =>\n parseLibsPayload(\n await api<{ libs?: unknown }>(`/list?session=${encodeURIComponent(sessionId)}`, undefined, 'GET'),\n ),\n add: async (sessionId: SessionId, path: string): Promise<void> => {\n await api('/add', { session: sessionId, path })\n },\n remove: async (sessionId: SessionId, id: string): Promise<void> => {\n await api('/remove', { session: sessionId, id })\n },\n pickDirectory: () => ctx.workspaces.pickDirectory(),\n listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal),\n })\n\n ctx.slots.inject('conversation.input.dock', () =>\n ctx.slots.register(\n {\n name: 'conversation.input.dock',\n id: 'ref-lib',\n // 排在最后面(goal 10 / queue 20 / git-graph 100 等之后):不与其他插件\n // 的 dock 入口抢位置;独立一行流式堆叠,天然不重叠。\n order: 200,\n locale: NS,\n inject: injected,\n },\n RefLibDock,\n ),\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/ref-lib` 命令参数解析的纯函数。
|
|
3
|
+
* @module @hpyperry/dsh-ref-lib/src/commands
|
|
4
|
+
*/
|
|
5
|
+
/** `/ref-lib` 支持的子命令。 */
|
|
6
|
+
export type RefLibCommand = {
|
|
7
|
+
readonly kind: 'list';
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: 'add';
|
|
10
|
+
readonly path: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'remove';
|
|
13
|
+
readonly id: string;
|
|
14
|
+
};
|
|
15
|
+
/** 解析失败的结果(含给用户看的错误文案)。 */
|
|
16
|
+
export interface RefLibCommandError {
|
|
17
|
+
readonly kind: 'error';
|
|
18
|
+
readonly text: string;
|
|
19
|
+
}
|
|
20
|
+
/** 解析结果:子命令或错误。 */
|
|
21
|
+
export type RefLibCommandResult = RefLibCommand | RefLibCommandError;
|
|
22
|
+
/** 用法提示,供 help 与错误文案复用。 */
|
|
23
|
+
export declare const REF_LIB_USAGE = "\u7528\u6CD5\uFF1A/ref-lib add <path> | /ref-lib list | /ref-lib remove <id>";
|
|
24
|
+
/**
|
|
25
|
+
* 把用户输入的路径解析为绝对路径:展开 `~`/`~/`,相对路径基于给定基准目录
|
|
26
|
+
* (当前会话工作区)解析。
|
|
27
|
+
* @param input - 用户输入的路径(可含空格)。
|
|
28
|
+
* @param base - 相对路径的解析基准(会话工作区绝对路径)。
|
|
29
|
+
* @returns 绝对路径。
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveRefLibPath(input: string, base: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* 解析 `command/run` 事件携带的 rawInput(命令名后的原文,含前导分隔空白)。
|
|
34
|
+
* @param rawInput - 命令名后的原文。
|
|
35
|
+
* @returns 解析出的子命令;无法解析时为错误结果。
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseRefLibCommand(rawInput: string): RefLibCommandResult;
|
|
38
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,yBAAyB;AACzB,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpD,2BAA2B;AAC3B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,mBAAmB;AACnB,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,kBAAkB,CAAA;AAEpE,2BAA2B;AAC3B,eAAO,MAAM,aAAa,iFAAkE,CAAA;AAE5F;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAkBxE"}
|
package/lib/commands.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/ref-lib` 命令参数解析的纯函数。
|
|
3
|
+
* @module @hpyperry/dsh-ref-lib/src/commands
|
|
4
|
+
*/
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
/** 用法提示,供 help 与错误文案复用。 */
|
|
8
|
+
export const REF_LIB_USAGE = '用法:/ref-lib add <path> | /ref-lib list | /ref-lib remove <id>';
|
|
9
|
+
/**
|
|
10
|
+
* 把用户输入的路径解析为绝对路径:展开 `~`/`~/`,相对路径基于给定基准目录
|
|
11
|
+
* (当前会话工作区)解析。
|
|
12
|
+
* @param input - 用户输入的路径(可含空格)。
|
|
13
|
+
* @param base - 相对路径的解析基准(会话工作区绝对路径)。
|
|
14
|
+
* @returns 绝对路径。
|
|
15
|
+
*/
|
|
16
|
+
export function resolveRefLibPath(input, base) {
|
|
17
|
+
const trimmed = input.trim();
|
|
18
|
+
if (trimmed === '~')
|
|
19
|
+
return homedir();
|
|
20
|
+
if (trimmed.startsWith('~/'))
|
|
21
|
+
return resolve(homedir(), trimmed.slice(2));
|
|
22
|
+
return resolve(base, trimmed);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 解析 `command/run` 事件携带的 rawInput(命令名后的原文,含前导分隔空白)。
|
|
26
|
+
* @param rawInput - 命令名后的原文。
|
|
27
|
+
* @returns 解析出的子命令;无法解析时为错误结果。
|
|
28
|
+
*/
|
|
29
|
+
export function parseRefLibCommand(rawInput) {
|
|
30
|
+
const trimmed = rawInput.trim();
|
|
31
|
+
if (trimmed === '')
|
|
32
|
+
return { kind: 'error', text: REF_LIB_USAGE };
|
|
33
|
+
const spaceAt = trimmed.search(/\s/);
|
|
34
|
+
const verb = spaceAt === -1 ? trimmed : trimmed.slice(0, spaceAt);
|
|
35
|
+
const rest = spaceAt === -1 ? '' : trimmed.slice(spaceAt).trim();
|
|
36
|
+
switch (verb) {
|
|
37
|
+
case 'list':
|
|
38
|
+
return rest === '' ? { kind: 'list' } : { kind: 'error', text: `list 不接受参数。${REF_LIB_USAGE}` };
|
|
39
|
+
case 'add':
|
|
40
|
+
return rest === '' ? { kind: 'error', text: `add 需要目录路径。${REF_LIB_USAGE}` } : { kind: 'add', path: rest };
|
|
41
|
+
case 'remove':
|
|
42
|
+
return rest === ''
|
|
43
|
+
? { kind: 'error', text: `remove 需要条目 id。${REF_LIB_USAGE}` }
|
|
44
|
+
: { kind: 'remove', id: rest };
|
|
45
|
+
default:
|
|
46
|
+
return { kind: 'error', text: `未知子命令 "${verb}"。${REF_LIB_USAGE}` };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAiBnC,2BAA2B;AAC3B,MAAM,CAAC,MAAM,aAAa,GAAG,+DAA+D,CAAA;AAE5F;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,IAAY;IAC3D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAC5B,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,OAAO,EAAE,CAAA;IACrC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IACzE,OAAO,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC/B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;IACjE,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;IACjE,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAA;IAChE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM;YACT,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,aAAa,EAAE,EAAE,CAAA;QAChG,KAAK,KAAK;YACR,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;QAC3G,KAAK,QAAQ;YACX,OAAO,IAAI,KAAK,EAAE;gBAChB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,aAAa,EAAE,EAAE;gBAC5D,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;QAClC;YACE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,IAAI,KAAK,aAAa,EAAE,EAAE,CAAA;IACtE,CAAC;AACH,CAAC"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ref-lib 插件入口(规范形态:default export Service 类)。
|
|
3
|
+
*
|
|
4
|
+
* 参考库为 **per-session** 数据:v3(2026-08-17 事故修复)起列表存于 dsh home
|
|
5
|
+
* 下的 sidecar JSON(`<dshHome>/plugin-data/ref-lib/<sessionId>.json`),会话间
|
|
6
|
+
* 完全隔离,**不再向会话日志写入自定义事件**(原因见 `service.ts` 头部说明)。
|
|
7
|
+
* v4 起 client 读/写走插件自注册的 `/api/ref-lib/*` HTTP 路由(`ctx.webServer`,
|
|
8
|
+
* 静默、无命令卡片)。
|
|
9
|
+
*
|
|
10
|
+
* - `ctx.refLibs` 服务:per-session 参考库校验与持久化(list / add / remove);
|
|
11
|
+
* - `/ref-lib` 命令(`ctx.commands`):操作**当前会话**的参考库;
|
|
12
|
+
* - `/api/ref-lib/*` 路由(`ctx.webServer` + `ctx.sessions`):client UI 静默读/写;
|
|
13
|
+
* - `reference-libs` 上下文贡献(`ctx.systemPrompt.context`):仅向配置了
|
|
14
|
+
* 参考库的会话注入其库清单与只读约束。
|
|
15
|
+
*
|
|
16
|
+
* 只读保证分层:
|
|
17
|
+
* - L1 沙箱天然只读:库位于 session workspace 之外时,read-only /
|
|
18
|
+
* workspace-write 模式下 bash/fs 均进程级只读(推荐用法);
|
|
19
|
+
* - L2 本插件注入的上下文软约束。
|
|
20
|
+
* @module @hpyperry/dsh-ref-lib
|
|
21
|
+
*/
|
|
22
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
23
|
+
import { type RefLibServiceConfig } from './service.ts';
|
|
24
|
+
/**
|
|
25
|
+
* ref-lib 插件本体:注册服务、命令与上下文贡献。
|
|
26
|
+
* 数据全部按当前会话(`invocation.agent.session` / `context.agent?.session`)操作。
|
|
27
|
+
*/
|
|
28
|
+
export declare class RefLibPlugin extends Service {
|
|
29
|
+
/**
|
|
30
|
+
* @param ctx - 宿主上下文。
|
|
31
|
+
* @param config - 可选存储根目录覆盖(测试用)。
|
|
32
|
+
*/
|
|
33
|
+
constructor(ctx: Context, config?: RefLibServiceConfig);
|
|
34
|
+
}
|
|
35
|
+
export default RefLibPlugin;
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAWtD,OAAO,EAAiB,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEtE;;;GAGG;AACH,qBAAa,YAAa,SAAQ,OAAO;IACvC;;;OAGG;gBACS,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,mBAAwB;CAuD3D;AAED,eAAe,YAAY,CAAA"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ref-lib 插件入口(规范形态:default export Service 类)。
|
|
3
|
+
*
|
|
4
|
+
* 参考库为 **per-session** 数据:v3(2026-08-17 事故修复)起列表存于 dsh home
|
|
5
|
+
* 下的 sidecar JSON(`<dshHome>/plugin-data/ref-lib/<sessionId>.json`),会话间
|
|
6
|
+
* 完全隔离,**不再向会话日志写入自定义事件**(原因见 `service.ts` 头部说明)。
|
|
7
|
+
* v4 起 client 读/写走插件自注册的 `/api/ref-lib/*` HTTP 路由(`ctx.webServer`,
|
|
8
|
+
* 静默、无命令卡片)。
|
|
9
|
+
*
|
|
10
|
+
* - `ctx.refLibs` 服务:per-session 参考库校验与持久化(list / add / remove);
|
|
11
|
+
* - `/ref-lib` 命令(`ctx.commands`):操作**当前会话**的参考库;
|
|
12
|
+
* - `/api/ref-lib/*` 路由(`ctx.webServer` + `ctx.sessions`):client UI 静默读/写;
|
|
13
|
+
* - `reference-libs` 上下文贡献(`ctx.systemPrompt.context`):仅向配置了
|
|
14
|
+
* 参考库的会话注入其库清单与只读约束。
|
|
15
|
+
*
|
|
16
|
+
* 只读保证分层:
|
|
17
|
+
* - L1 沙箱天然只读:库位于 session workspace 之外时,read-only /
|
|
18
|
+
* workspace-write 模式下 bash/fs 均进程级只读(推荐用法);
|
|
19
|
+
* - L2 本插件注入的上下文软约束。
|
|
20
|
+
* @module @hpyperry/dsh-ref-lib
|
|
21
|
+
*/
|
|
22
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
23
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
24
|
+
import { parseRefLibCommand, REF_LIB_USAGE, resolveRefLibPath } from "./commands.js";
|
|
25
|
+
import { renderLibList, renderRefLibs } from "./render.js";
|
|
26
|
+
import { makeRefLibRoutes } from "./routes.js";
|
|
27
|
+
import { RefLibService } from "./service.js";
|
|
28
|
+
/**
|
|
29
|
+
* ref-lib 插件本体:注册服务、命令与上下文贡献。
|
|
30
|
+
* 数据全部按当前会话(`invocation.agent.session` / `context.agent?.session`)操作。
|
|
31
|
+
*/
|
|
32
|
+
export class RefLibPlugin extends Service {
|
|
33
|
+
/**
|
|
34
|
+
* @param ctx - 宿主上下文。
|
|
35
|
+
* @param config - 可选存储根目录覆盖(测试用)。
|
|
36
|
+
*/
|
|
37
|
+
constructor(ctx, config = {}) {
|
|
38
|
+
super(ctx, 'refLibPlugin');
|
|
39
|
+
const refLibs = new RefLibService(ctx, config);
|
|
40
|
+
// v4:client UI 静默读/写通道 —— 在宿主 webServer 上注册 /api/ref-lib/* 路由。
|
|
41
|
+
// 仅 web 组合存在 webServer/sessions 时生效(inject 等待,缺服务则不注册)。
|
|
42
|
+
ctx.inject(['webServer', 'sessions'], (routeCtx) => {
|
|
43
|
+
const routes = makeRefLibRoutes({
|
|
44
|
+
refLibs,
|
|
45
|
+
resolveSession: (sessionId) => routeCtx.sessions.get(SessionId(sessionId)),
|
|
46
|
+
log: (message) => ctx.logger.info(message),
|
|
47
|
+
});
|
|
48
|
+
for (const route of routes)
|
|
49
|
+
routeCtx.webServer.register(route);
|
|
50
|
+
});
|
|
51
|
+
ctx.inject(['commands'], (commandsCtx) => {
|
|
52
|
+
commandsCtx.commands.register({
|
|
53
|
+
name: 'ref-lib',
|
|
54
|
+
description: '管理本会话的只读参考库(add <path> / list / remove <id>)',
|
|
55
|
+
input: { hint: 'add <path> | list | remove <id>' },
|
|
56
|
+
handler: async (invocation) => {
|
|
57
|
+
const parsed = parseRefLibCommand(invocation.rawInput);
|
|
58
|
+
if (parsed.kind === 'error')
|
|
59
|
+
return { kind: 'error', text: parsed.text };
|
|
60
|
+
try {
|
|
61
|
+
const session = invocation.agent.session;
|
|
62
|
+
if (parsed.kind === 'list') {
|
|
63
|
+
return { kind: 'success', text: renderLibList(refLibs.list(session)) };
|
|
64
|
+
}
|
|
65
|
+
if (parsed.kind === 'add') {
|
|
66
|
+
// 相对路径基于当前会话工作区解析(`~` 亦展开)。
|
|
67
|
+
const base = session.header.cwd ?? process.cwd();
|
|
68
|
+
const entry = await refLibs.add(session, resolveRefLibPath(parsed.path, base));
|
|
69
|
+
return { kind: 'success', text: `已添加只读参考库:${entry.path}` };
|
|
70
|
+
}
|
|
71
|
+
await refLibs.remove(session, parsed.id);
|
|
72
|
+
return { kind: 'success', text: `已移除参考库条目:${parsed.id}` };
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
+
return { kind: 'error', text: `${message}${REF_LIB_USAGE}` };
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
ctx.inject(['systemPrompt'], (promptCtx) => {
|
|
82
|
+
promptCtx.systemPrompt.context({
|
|
83
|
+
name: 'reference-libs',
|
|
84
|
+
order: 150,
|
|
85
|
+
text: (context) => {
|
|
86
|
+
const session = context.agent?.session;
|
|
87
|
+
return session === undefined ? '' : renderRefLibs(refLibs.list(session));
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export default RefLibPlugin;
|
|
94
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/logic.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 参考库列表折叠与变换的纯函数:不依赖 session / 文件系统,便于单元测试。
|
|
3
|
+
* @module @hpyperry/dsh-ref-lib/src/logic
|
|
4
|
+
*/
|
|
5
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
6
|
+
import type { RefLibEntry } from './spec.ts';
|
|
7
|
+
/**
|
|
8
|
+
* 从 session 事件流折叠出参考库列表(取最后一个合法 `ref-lib/set` 的完整快照;
|
|
9
|
+
* 无事件时为初始空列表)。v3 起仅用于迁移旧日志中已存在的 `ref-lib/set` 事件。
|
|
10
|
+
* 防御性校验:旧日志/手改数据可能畸形(libs 非数组、条目缺字段),畸形事件被
|
|
11
|
+
* 忽略并保留上一个有效快照——`list()` 的冷读路径会在 systemPrompt context 回调
|
|
12
|
+
* 中被调用(无 try/catch),不能在这里抛错。
|
|
13
|
+
* @param events - session 事件流(可能含历史 `ref-lib/set`)。
|
|
14
|
+
* @returns 当前参考库列表。
|
|
15
|
+
*/
|
|
16
|
+
export declare function foldRefLibs(events: readonly SessionEvent[]): RefLibEntry[];
|
|
17
|
+
/**
|
|
18
|
+
* 追加一个新条目。已存在同 id 或同 path(规范化后)的条目时不重复添加,
|
|
19
|
+
* 返回原列表不变(幂等)。
|
|
20
|
+
* @param list - 现有列表。
|
|
21
|
+
* @param entry - 待追加条目。
|
|
22
|
+
* @returns 追加后的新列表。
|
|
23
|
+
*/
|
|
24
|
+
export declare function upsertLib(list: readonly RefLibEntry[], entry: RefLibEntry): RefLibEntry[];
|
|
25
|
+
/**
|
|
26
|
+
* 按 id 移除条目;未知 id 幂等返回原列表。
|
|
27
|
+
* @param list - 现有列表。
|
|
28
|
+
* @param id - 待移除条目 id。
|
|
29
|
+
* @returns 移除后的新列表。
|
|
30
|
+
*/
|
|
31
|
+
export declare function removeLib(list: readonly RefLibEntry[], id: string): RefLibEntry[];
|
|
32
|
+
//# sourceMappingURL=logic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logic.d.ts","sourceRoot":"","sources":["../src/logic.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAG5C;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,WAAW,EAAE,CAQ1E;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,WAAW,EAAE,CAKzF;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,EAAE,CAEjF"}
|
package/lib/logic.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 参考库列表折叠与变换的纯函数:不依赖 session / 文件系统,便于单元测试。
|
|
3
|
+
* @module @hpyperry/dsh-ref-lib/src/logic
|
|
4
|
+
*/
|
|
5
|
+
import { isRefLibEntry } from "./validate.js";
|
|
6
|
+
/**
|
|
7
|
+
* 从 session 事件流折叠出参考库列表(取最后一个合法 `ref-lib/set` 的完整快照;
|
|
8
|
+
* 无事件时为初始空列表)。v3 起仅用于迁移旧日志中已存在的 `ref-lib/set` 事件。
|
|
9
|
+
* 防御性校验:旧日志/手改数据可能畸形(libs 非数组、条目缺字段),畸形事件被
|
|
10
|
+
* 忽略并保留上一个有效快照——`list()` 的冷读路径会在 systemPrompt context 回调
|
|
11
|
+
* 中被调用(无 try/catch),不能在这里抛错。
|
|
12
|
+
* @param events - session 事件流(可能含历史 `ref-lib/set`)。
|
|
13
|
+
* @returns 当前参考库列表。
|
|
14
|
+
*/
|
|
15
|
+
export function foldRefLibs(events) {
|
|
16
|
+
let libs = [];
|
|
17
|
+
for (const event of events) {
|
|
18
|
+
if (event.type !== 'ref-lib/set')
|
|
19
|
+
continue;
|
|
20
|
+
const data = event.data?.libs;
|
|
21
|
+
if (Array.isArray(data))
|
|
22
|
+
libs = data.filter(isRefLibEntry);
|
|
23
|
+
}
|
|
24
|
+
return libs;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 追加一个新条目。已存在同 id 或同 path(规范化后)的条目时不重复添加,
|
|
28
|
+
* 返回原列表不变(幂等)。
|
|
29
|
+
* @param list - 现有列表。
|
|
30
|
+
* @param entry - 待追加条目。
|
|
31
|
+
* @returns 追加后的新列表。
|
|
32
|
+
*/
|
|
33
|
+
export function upsertLib(list, entry) {
|
|
34
|
+
if (list.some((existing) => existing.id === entry.id || existing.path === entry.path)) {
|
|
35
|
+
return [...list];
|
|
36
|
+
}
|
|
37
|
+
return [...list, entry];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 按 id 移除条目;未知 id 幂等返回原列表。
|
|
41
|
+
* @param list - 现有列表。
|
|
42
|
+
* @param id - 待移除条目 id。
|
|
43
|
+
* @returns 移除后的新列表。
|
|
44
|
+
*/
|
|
45
|
+
export function removeLib(list, id) {
|
|
46
|
+
return list.filter((entry) => entry.id !== id);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=logic.js.map
|
package/lib/logic.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logic.js","sourceRoot":"","sources":["../src/logic.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE7C;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,MAA+B;IACzD,IAAI,IAAI,GAAkB,EAAE,CAAA;IAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa;YAAE,SAAQ;QAC1C,MAAM,IAAI,GAAI,KAAK,CAAC,IAAuC,EAAE,IAAI,CAAA;QACjE,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;IAC5D,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAA4B,EAAE,KAAkB;IACxE,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtF,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAA;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAA4B,EAAE,EAAU;IAChE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;AAChD,CAAC"}
|
package/lib/render.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* systemPrompt 注入文本的纯渲染函数。
|
|
3
|
+
* @module @hpyperry/dsh-ref-lib/src/render
|
|
4
|
+
*/
|
|
5
|
+
import type { RefLibEntry } from './spec.ts';
|
|
6
|
+
/**
|
|
7
|
+
* 渲染参考库清单与只读约束声明。空列表返回空串(不占用模型上下文 token)。
|
|
8
|
+
* @param libs - 已注册的只读参考库。
|
|
9
|
+
* @returns 注入到系统提示的文本;无库时为空串。
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderRefLibs(libs: readonly RefLibEntry[]): string;
|
|
12
|
+
/**
|
|
13
|
+
* 渲染 `/ref-lib list` 的人类可读输出。
|
|
14
|
+
* @param libs - 已注册的只读参考库。
|
|
15
|
+
* @returns 列表文本;无库时提示为空。
|
|
16
|
+
*/
|
|
17
|
+
export declare function renderLibList(libs: readonly RefLibEntry[]): string;
|
|
18
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAG5C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAYlE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAQlE"}
|
package/lib/render.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* systemPrompt 注入文本的纯渲染函数。
|
|
3
|
+
* @module @hpyperry/dsh-ref-lib/src/render
|
|
4
|
+
*/
|
|
5
|
+
import { sanitizeControlCharacters } from "./validate.js";
|
|
6
|
+
/**
|
|
7
|
+
* 渲染参考库清单与只读约束声明。空列表返回空串(不占用模型上下文 token)。
|
|
8
|
+
* @param libs - 已注册的只读参考库。
|
|
9
|
+
* @returns 注入到系统提示的文本;无库时为空串。
|
|
10
|
+
*/
|
|
11
|
+
export function renderRefLibs(libs) {
|
|
12
|
+
if (libs.length === 0)
|
|
13
|
+
return '';
|
|
14
|
+
const lines = libs.map((entry) => {
|
|
15
|
+
const note = entry.note === undefined || entry.note === '' ? '' : `(${sanitizeControlCharacters(entry.note)})`;
|
|
16
|
+
return `- ${sanitizeControlCharacters(entry.path)}${note}`;
|
|
17
|
+
});
|
|
18
|
+
return [
|
|
19
|
+
'只读参考库:以下目录作为可参考库按需使用——不要主动遍历或全量读取,仅当任务需要时才读取具体文件(大文件先读其索引/README)。',
|
|
20
|
+
'查询优先级:当需要查证信息、规范或依据时,优先在上述参考库目录内检索(先读其索引/README,再按需读取具体文件),确认参考库未覆盖或不足以回答后,才改用其他途径(如网络搜索等)。',
|
|
21
|
+
'只读约束:禁止创建、修改或删除其中任何文件;如需改动,先把文件复制到当前工作区。',
|
|
22
|
+
...lines,
|
|
23
|
+
].join('\n');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 渲染 `/ref-lib list` 的人类可读输出。
|
|
27
|
+
* @param libs - 已注册的只读参考库。
|
|
28
|
+
* @returns 列表文本;无库时提示为空。
|
|
29
|
+
*/
|
|
30
|
+
export function renderLibList(libs) {
|
|
31
|
+
if (libs.length === 0)
|
|
32
|
+
return '当前没有已注册的只读参考库。使用 /ref-lib add <path> 添加。';
|
|
33
|
+
return libs
|
|
34
|
+
.map((entry) => {
|
|
35
|
+
const note = entry.note === undefined || entry.note === '' ? '' : `(${sanitizeControlCharacters(entry.note)})`;
|
|
36
|
+
return `- ${entry.id}: ${sanitizeControlCharacters(entry.path)}${note}`;
|
|
37
|
+
})
|
|
38
|
+
.join('\n');
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAA;AAEzD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAA4B;IACxD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAA;QAC9G,OAAO,KAAK,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAA;IAC5D,CAAC,CAAC,CAAA;IACF,OAAO;QACL,oEAAoE;QACpE,6FAA6F;QAC7F,0CAA0C;QAC1C,GAAG,KAAK;KACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAA4B;IACxD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,0CAA0C,CAAA;IACxE,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAA;QAC9G,OAAO,KAAK,KAAK,CAAC,EAAE,KAAK,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAA;IACzE,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC"}
|
package/lib/routes.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ref-lib 管理路由:`/api/ref-lib/*`。
|
|
3
|
+
*
|
|
4
|
+
* client(浏览器端)经**普通同源 fetch** 访问这些路由——这是插件在宿主
|
|
5
|
+
* `ctx.webServer`(`@deepseek-ai/dsh-host-webserver`)上自注册的自定义 HTTP
|
|
6
|
+
* 路由,静默双向、不渲染命令卡片(参照 dsh-ssh / dsh-persona-memory 先例)。
|
|
7
|
+
* v4 起 client 读/写不再走 `/ref-lib` 命令分发。
|
|
8
|
+
*
|
|
9
|
+
* 安全:所有路由 loopback-only(同源 + 回环地址护栏,镜像 dsh-ssh 的
|
|
10
|
+
* `isLoopbackRequest`)——这些端点会读改 sidecar 状态,LAN 暴露的部署不能开放。
|
|
11
|
+
* @module @hpyperry/dsh-ref-lib/src/routes
|
|
12
|
+
*/
|
|
13
|
+
import type { IncomingMessage } from 'node:http';
|
|
14
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
15
|
+
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
|
|
16
|
+
import { type RefLibService } from './service.ts';
|
|
17
|
+
/** 请求体大小上限(管理载荷都很小)。 */
|
|
18
|
+
export declare const MAX_JSON_BODY_BYTES: number;
|
|
19
|
+
/** Loopback 校验:回环源地址 + 同源 Host/Origin(镜像 dsh-ssh 的护栏)。 */
|
|
20
|
+
export declare function isLoopbackRequest(request: IncomingMessage): boolean;
|
|
21
|
+
/** makeRefLibRoutes 的依赖面(测试可注入假实现)。 */
|
|
22
|
+
export interface RefLibRouteDeps {
|
|
23
|
+
/** per-session 参考库服务。 */
|
|
24
|
+
refLibs: RefLibService;
|
|
25
|
+
/** 会话 id → 活会话;UI 面板总是指向当前会话(必为 live),非 live 返回 undefined。 */
|
|
26
|
+
resolveSession: (sessionId: string) => Session | undefined;
|
|
27
|
+
/** 日志。 */
|
|
28
|
+
log: (message: string) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 构建 `/api/ref-lib/*` 路由族。
|
|
32
|
+
* - `GET /api/ref-lib/list?session=<id>` → `{ libs }`
|
|
33
|
+
* - `POST /api/ref-lib/add { session, path }` → `{ entry }`
|
|
34
|
+
* - `POST /api/ref-lib/remove { session, id }` → `{ ok: true }`
|
|
35
|
+
* @param deps - 依赖面。
|
|
36
|
+
* @returns 待注册到 `ctx.webServer` 的路由数组。
|
|
37
|
+
*/
|
|
38
|
+
export declare function makeRefLibRoutes(deps: RefLibRouteDeps): WebRoute[];
|
|
39
|
+
//# sourceMappingURL=routes.d.ts.map
|