dsh-remote-file-system 0.1.2 → 0.2.1
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/README.md +43 -14
- package/lib/client.js +553 -0
- package/lib/client.js.map +7 -0
- package/lib/index.js +156 -4
- package/lib/index.js.map +3 -3
- package/package.json +20 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/client.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * dsh-remote-file-system 客户端半身。\n *\n * 内置右侧栏的 text 类型只认主机本地文件:点开 wsl:~/x.md 这类远程路径时,\n * openFile 固定用 fileAddressFor 造出 dsh-resource://file/session/<id>/wsl:~/x.md,\n * 内置查看器经 ctx.fs 在本地查找而失败。\n *\n * 本类型以 extension 优先级认领这些伪 file 地址,从地址里解析出别名与远端路径,\n * 经 Host 自注册的 /remote-file-system 通道读取远端文本并渲染。\n * 只读:写入仍走 read_remote、write_remote、edit_remote 三条工具。\n *\n * 正文沿用内置文档预览的外观:纯文本按源码行渲染,代码文件交给共享的 CodeBlock,\n * 头部是同样 38px 的路径栏与换行、重新加载图标按钮。\n *\n * @module dsh-remote-file-system/client\n */\n\nimport * as React from 'react'\nimport {\n CodeBlock,\n FileTypeIcon,\n IconNowrapFillRegular,\n IconRefreshOutlineRegular,\n IconWrapFillRegular,\n Menu,\n PathLabel,\n StateDot,\n Tooltip,\n classifyFileType,\n languageForPath,\n} from '@deepseek-ai/dsh-client-ui-primitives'\n\n// 常量\n\n/** 本插件在 tab 注册表里的类型身份,同时用作正文席位 key。 */\nconst TYPE_ID = 'dsh-remote-file-system'\n\n/** tab 类型的判别名。内置已有 guide、text、files、browser、subagentchat。 */\nconst KIND = 'remote'\n\n/** 会话文件资源地址前缀,与 util/workspace-path 的语法一致。 */\nconst FILE_PREFIX = 'dsh-resource://file/session/'\n\n/** 别名字符集,与 host 端 splitAliasPath 的 ALIAS_RE 一致。 */\nconst ALIAS_RE = /^[A-Za-z0-9._-]+$/\n\n/** 单字母别名排除:Windows 盘符 C 与 D 也长这样,本地路径不能被抢。 */\nconst SINGLE_LETTER_RE = /^[A-Za-z]$/\n\n/** 正文最多渲染的行数,超出提示,避免超大文件卡住浏览器。 */\nconst MAX_RENDER_LINES = 5000\n\n// 地址解析\n\n/** 一个被认领的远程文件地址。 */\ninterface RemoteTarget {\n /** 原始资源地址。 */\n address: string\n /** ssh 别名,取自 ~/.ssh/config。 */\n alias: string\n /** 远端绝对路径或 ~ 开头路径。 */\n rawPath: string\n /** 复原成 host 端认识的形式,即「别名:路径」。 */\n target: string\n}\n\n/**\n * 从 dsh-resource 文件地址里解析远程目标。\n * 只认 session 作用域,absolute 作用域没有授权会话,也被 host 拒绝。\n * @param address - 资源地址。\n */\nfunction parseRemoteAddress(address: string): RemoteTarget | null {\n if (!address.startsWith(FILE_PREFIX)) return null\n const rest = address.slice(FILE_PREFIX.length)\n const slash = rest.indexOf('/')\n if (slash <= 0) return null\n let path: string\n try {\n path = rest.slice(slash + 1).split('/').map(decodeURIComponent).join('/')\n } catch {\n return null\n }\n const colon = path.indexOf(':')\n if (colon <= 0) return null\n const alias = path.slice(0, colon)\n const rawPath = path.slice(colon + 1)\n if (!ALIAS_RE.test(alias) || SINGLE_LETTER_RE.test(alias)) return null\n if (!(rawPath.startsWith('/') || rawPath.startsWith('~/'))) return null\n return { address, alias, rawPath, target: alias + ':' + rawPath }\n}\n\n// Host 服务面\n\n/** 右侧栏导航控制器最小面。 */\ninterface SidebarRightService {\n openResource(address: string, options?: { kind?: string; replaceTab?: string; revealIfOpened?: boolean; params?: unknown }): void\n active(): { id?: string; contentId?: string } | undefined\n}\n\n/** 右侧栏 tab 类型注册表最小面。 */\ninterface TabsService {\n register(definition: {\n id: string\n kind: string\n patterns?: string[]\n priority?: 'extension' | 'builtin' | 'fallback'\n canOpen?(address: string): boolean\n title(address: string): string\n keepMounted?: boolean\n }): () => void\n}\n\n/** 槽位服务最小面:keyed 席位按注册项 key 定址。 */\ninterface SlotsService {\n inject(name: string, callback: () => (() => void) | void): void\n register<P>(\n options: { name: string; id?: string; key?: string; order?: number; priority?: number },\n component: (props: P) => React.ReactElement,\n ): () => void\n}\n\n/** 客户端插件上下文最小面。 */\ninterface ClientContext {\n get(service: string): unknown\n effect(callback: () => (() => void) | void): void\n}\n\n/** 框架交给正文与标题的 tab 信息,由 slot 声明里的 hooks.tabInfo 转换为 useTabInfo。 */\ninterface RemoteTabInfo {\n tab: {\n id: string\n title: string\n navigation: { address: string; params?: { line?: number } }\n signal: AbortSignal\n }\n}\n\n/** 正文组件的注入面。 */\ninterface TabBodyProps {\n useTabInfo(): RemoteTabInfo\n}\n\n/** tab 标题组件的注入面。 */\ninterface TabTitleProps {\n useTabInfo(): RemoteTabInfo\n}\n\n// RPC 调用\n\nlet rpcIdCounter = 0\n\n/**\n * 调用 Host 端 RPC 端点。\n * 格式:POST /remote-file-system/<method>,独立 RPC 通道,\n * 遵循 DSH 四象限消息模型:body.method 必须等于 URL 中的端点名。\n * @param method - 端点名。\n * @param payload - 端点载荷。\n * @param signal - tab 记录消失时中止请求的取消信号。\n */\nasync function rpcCall<T>(method: string, payload: unknown, signal?: AbortSignal): Promise<T> {\n const rpcId = 'rfs-' + String(++rpcIdCounter)\n const body = JSON.stringify({\n type: 'client-request',\n rpcId: rpcId,\n method: method,\n payload: payload,\n })\n const res = await fetch('/remote-file-system/' + method, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: body,\n ...(signal !== undefined ? { signal } : {}),\n })\n if (!res.ok) throw new Error('HTTP ' + res.status)\n const envelope = await res.json() as {\n result?: { ok?: boolean; value?: unknown; error?: { message?: string } }\n }\n if (envelope.result && envelope.result.ok) return envelope.result.value as T\n const errMsg = envelope.result && envelope.result.error\n ? envelope.result.error.message\n : 'RPC 调用失败'\n throw new Error(errMsg)\n}\n\n/** read 端点的返回值。 */\ninterface ReadResult {\n path: string\n text: string\n bytes: number\n truncated: boolean\n}\n\n// 样式注入\n\n/** 预览外观对齐内置文档预览:38px 头部、13px 等宽正文与同一组图标按钮。 */\nconst CSS = [\n '.dsh-rfs-preview{flex-direction:column;flex:auto;height:100%;min-height:0;display:flex}',\n '.dsh-rfs-header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:4px;height:38px;padding:0 6px 0 16px;display:flex}',\n '.dsh-rfs-path{margin-right:12px}',\n '.dsh-rfs-titleIcon{flex:none}',\n '.dsh-rfs-notice{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border-bottom:.5px solid var(--dsw-alias-border-l1);flex:none;align-items:center;gap:10px;margin:0;padding:6px 10px;font-size:12px;display:flex}',\n '.dsh-rfs-body{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);min-height:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-mono,ui-monospace,monospace);white-space:pre;flex:auto;margin:0;padding:0;line-height:1.6;position:relative;overflow:auto}',\n '.dsh-rfs-body::-webkit-scrollbar-track{margin:2px}',\n '.dsh-rfs-body[data-code]{flex-direction:column;display:flex;overflow:hidden}',\n '.dsh-rfs-wrap{white-space:pre-wrap;word-break:break-word}',\n '.dsh-rfs-textDocument{box-sizing:border-box;min-width:100%;min-height:100%;padding:8px}',\n '.dsh-rfs-page{font:var(--dsw-font-markdown-code-block);white-space:inherit;margin:0}',\n '.dsh-rfs-line{padding:0 10px}',\n '.dsh-rfs-lineTarget{background:var(--dsw-alias-interactive-bg-hover)}',\n '.dsh-rfs-empty{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-family);text-align:center;white-space:normal;flex-direction:column;justify-content:center;align-items:center;gap:16px;padding:0 24px;line-height:1.6;display:flex}',\n '.dsh-rfs-empty:after{content:\"\";flex:0 12%}',\n '.dsh-rfs-emptyIcon{opacity:.6;filter:grayscale();flex:none}',\n '.dsh-rfs-emptyLine{margin:0}',\n '.dsh-rfs-retry{height:32px;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);cursor:pointer;background:0 0;flex:none;align-items:center;gap:6px;padding:0 14px 0 12px;font-family:inherit;display:inline-flex}',\n '.dsh-rfs-retry:hover{background:var(--dsw-alias-interactive-bg-hover)}',\n '.dsh-rfs-status{box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:8px;height:100%;padding:12px 10px;display:flex}',\n '.dsh-rfs-statusLine{color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);white-space:normal;align-items:center;gap:10px;margin:0;padding:6px 10px;line-height:1.6;display:flex}',\n '.dsh-rfs-tool{width:28px;height:28px;color:var(--dsw-alias-label-secondary);border-radius:var(--dsw-radius-sm);cursor:pointer;background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:6px;line-height:1;display:inline-flex}',\n '.dsh-rfs-tool svg{width:15px;height:15px}',\n '.dsh-rfs-tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}',\n '.dsh-rfs-viewerTool{width:auto;max-width:160px;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;padding:0 6px;font-size:12px;overflow:hidden}',\n '.dsh-rfs-code{white-space:normal;flex-direction:column;flex:auto;width:100%;min-width:0;height:100%;min-height:0;display:flex;overflow:hidden}',\n '.dsh-rfs-code .dsh-rfs-codeblock{--dsl-code-block-border-radius:0px;--dsl-code-block-line-white-space:pre;--dsl-code-block-background:transparent;flex-direction:column;flex:auto;min-width:0;height:100%;min-height:0;margin:0;display:flex;position:static}',\n '.dsh-rfs-code .dsh-rfs-codeblock>[data-code-block-content]{flex:auto;min-width:0;min-height:0;display:block;position:relative;overflow:auto}',\n '.dsh-rfs-code .dsh-rfs-codeblock>[data-code-block-content]::-webkit-scrollbar-track{margin:2px}',\n '.dsh-rfs-code .dsh-rfs-codeblock pre{box-sizing:border-box;white-space:pre;word-break:normal;overflow-wrap:normal;min-width:100%;padding:16px;overflow:visible}',\n '.dsh-rfs-code[data-wrap=true] .dsh-rfs-codeblock{--dsl-code-block-line-white-space:pre-wrap}',\n '.dsh-rfs-code[data-wrap=true] .dsh-rfs-codeblock pre{white-space:pre-wrap;overflow-wrap:anywhere}',\n '.dsh-rfs-root{display:flex;flex-direction:column;width:100%;min-width:0}',\n '.dsh-rfs-row{position:relative;overflow:hidden;display:flex;align-items:center;height:calc(24px + var(--dsh-content-font-delta,0px));min-width:0;color:var(--dsw-alias-label-tertiary);transition:color 100ms ease}',\n '.dsh-rfs-row[data-expandable]{cursor:pointer}',\n '.dsh-rfs-row:hover{color:var(--dsw-alias-label-secondary)}',\n '.dsh-rfs-leading{position:relative;flex:none;width:calc(16px + var(--dsh-content-font-delta,0px));height:calc(16px + var(--dsh-content-font-delta,0px));display:inline-flex;align-items:center;justify-content:center;margin-right:6px;padding:0;border:none;background:none;color:inherit}',\n '.dsh-rfs-leading svg:not([data-state]){width:calc(14px + var(--dsh-content-font-delta,0px));height:calc(14px + var(--dsh-content-font-delta,0px))}',\n '.dsh-rfs-content{display:flex;align-items:center;flex:1 1 auto;min-width:0}',\n '.dsh-rfs-rowtitle{flex:none;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:inherit}',\n '.dsh-rfs-sep{flex:none;width:2px;height:2px;border-radius:1px;margin:0 8px;background:var(--dsw-alias-label-caption)}',\n '.dsh-rfs-summary{flex:1 1 auto;min-width:0;display:flex;align-items:center;overflow:hidden;font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px))}',\n '.dsh-rfs-summarytext{flex:none;white-space:nowrap}',\n '.dsh-rfs-summarylink{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin:0;padding:0;border:none;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer}',\n '.dsh-rfs-summarylink:hover{text-decoration:underline;text-underline-offset:3px}',\n '.dsh-rfs-iocard{display:flex;flex-direction:column;margin:4px 0 4px 4px;border:0.5px solid var(--dsw-alias-border-l1);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-markdown-code-block);font:var(--dsw-font-markdown-code-block-small)}',\n '.dsh-rfs-iosection{display:grid;grid-template-columns:max-content 1fr;column-gap:14px;align-items:baseline;padding:12px 16px;max-height:150px;overflow-y:auto}',\n '.dsh-rfs-iolabel{position:sticky;top:0;align-self:start;color:var(--dsw-alias-label-caption)}',\n '.dsh-rfs-iodivider{flex:none;height:0.5px;background:var(--dsw-alias-border-l2)}',\n '.dsh-rfs-iotext{min-width:0;white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-secondary)}',\n '.dsh-rfs-iotext[data-error]{color:var(--dsw-alias-state-error-primary)}',\n].join('\\n')\n\n/** 插件样式表元素,随插件生命周期存在。 */\nlet styleTag: HTMLStyleElement | null = null\n\n/** 注入插件样式表,幂等。 */\nfunction ensureStyle(): void {\n if (styleTag) return\n const tag = document.createElement('style')\n // 样式必须自带归属标记:client 模块系统在实例化插件时会认领页面上无 data-plugin 的 style,\n // 别的插件随后重载就按 id 把它们一起删掉,本插件样式随即失效。\n tag.dataset.plugin = 'dsh-remote-file-system'\n tag.dataset.pluginCss = 'dsh-remote-file-system/client.css'\n tag.textContent = CSS\n document.head.appendChild(tag)\n styleTag = tag\n}\n\n/** 移除插件样式表。 */\nfunction removeStyle(): void {\n if (styleTag && styleTag.parentNode) {\n styleTag.parentNode.removeChild(styleTag)\n }\n styleTag = null\n}\n\n/** 内置预览器视为文档、不走代码渲染的语言。 */\nconst DOCUMENT_LANGUAGES = new Set(['markdown', 'mdx', 'rst', 'latex', 'bibtex', 'asciidoc'])\n\n/**\n * 按远端路径选择内置预览器会用的代码语言。\n * 文档类语言仍按纯文本展示,与内置 Markdown 渲染器的分工一致。\n * @param path - 远端路径。\n * @returns 代码高亮语言,纯文本时为 null。\n */\nfunction codeLanguageOf(path: string): string | null {\n const language = languageForPath(path)\n if (language === undefined || DOCUMENT_LANGUAGES.has(language)) return null\n return language\n}\n\n/** 取路径最后一段,作为 tab 标题,与内置预览器的 basename 一致。 */\nfunction basenameOf(path: string): string {\n const name = path.slice(path.lastIndexOf('/') + 1)\n return name === '' ? path : name\n}\n\n// 正文组件\n\n/**\n * 一个 remote tab 的正文:读取远端文本,按内置文档预览的两种形态渲染。\n * 代码文件交给共享的 CodeBlock,纯文本按源码行渲染。\n * 地址解析失败或读取失败时只渲染原生样式的空态,不抛错崩溃。\n * @param props - 框架注入的 tab 信息钩子。\n */\nfunction RemoteFileBody(props: TabBodyProps): React.ReactElement {\n const info = props.useTabInfo()\n const address = info.tab.navigation.address\n const params = info.tab.navigation.params\n const line = params && typeof params.line === 'number' ? params.line : undefined\n const target = React.useMemo(function () { return parseRemoteAddress(address) }, [address])\n const language = React.useMemo(function () { return target ? codeLanguageOf(target.rawPath) : null }, [target])\n\n const [phase, setPhase] = React.useState<'loading' | 'ready' | 'error'>('loading')\n const [text, setText] = React.useState('')\n const [truncated, setTruncated] = React.useState(false)\n const [message, setMessage] = React.useState('')\n const [revision, setRevision] = React.useState(0)\n const [wrap, setWrap] = React.useState(true)\n // 代码文件与内置预览一样可在「代码」与「纯文本」之间切换;documents 只有一个渲染器时不显示菜单。\n const [plain, setPlain] = React.useState(false)\n const [menuOpen, setMenuOpen] = React.useState(false)\n const showCode = language !== null && !plain\n const bodyRef = React.useRef<HTMLDivElement | null>(null)\n\n // 读取内容:地址变化或手动刷新即重读。取消标记避免旧请求覆盖新 tab 的正文。\n React.useEffect(function () {\n if (!target) {\n setPhase('error')\n setMessage('无法识别的远程文件地址')\n return\n }\n let cancelled = false\n setPhase('loading')\n setMessage('')\n rpcCall<ReadResult>('read', { target: target.target }, info.tab.signal).then(function (result) {\n if (cancelled) return\n setText(result.text)\n setTruncated(result.truncated)\n setPhase('ready')\n }).catch(function (e: unknown) {\n if (cancelled) return\n setPhase('error')\n setMessage(e instanceof Error ? e.message : String(e))\n })\n return function () { cancelled = true }\n }, [address, revision])\n\n // 内容就绪后按 navigation.params.line 定位:与内置预览一样直接写滚动容器的 scrollTop。\n React.useEffect(function () {\n if (phase !== 'ready' || line === undefined) return\n const body = bodyRef.current\n if (body === null) return\n const scroller = showCode ? body.querySelector('[data-code-block-content]') : body\n if (!(scroller instanceof HTMLElement)) return\n const row = showCode\n ? body.querySelectorAll('.dsh-rfs-codeblock pre .line').item(line - 1)\n : body.querySelector('[data-line=\"' + line + '\"]')\n if (row instanceof HTMLElement) scroller.scrollTop = Math.max(0, row.offsetTop)\n }, [phase, text, line, showCode])\n\n // 按行切分,去掉尾随换行产生的空行,再按上限截断并记录是否真的截断。\n const view = React.useMemo(function () {\n if (text === '') return { rows: [] as string[], capped: false }\n const parts = text.split('\\n')\n if (parts.length > 0 && parts[parts.length - 1] === '') parts.pop()\n return { rows: parts.slice(0, MAX_RENDER_LINES), capped: parts.length > MAX_RENDER_LINES }\n }, [text])\n\n function refresh(): void { setRevision(function (r) { return r + 1 }) }\n\n const notice = phase === 'ready' && (truncated || view.capped)\n ? (view.capped ? '仅显示前 ' + MAX_RENDER_LINES + ' 行' : '文件较大,已截断')\n : null\n\n const body = React.createElement('div', {\n ref: bodyRef,\n className: wrap ? 'dsh-rfs-body dsh-rfs-wrap' : 'dsh-rfs-body',\n ...(showCode ? { 'data-code': true } : {}),\n },\n phase === 'loading' ? React.createElement('div', { className: 'dsh-rfs-status' },\n React.createElement(StateDot, { state: 'ongoing', size: 28 }),\n React.createElement('p', { className: 'dsh-rfs-statusLine' }, '文档渲染中...'),\n ) : null,\n phase === 'error' ? React.createElement('div', { className: 'dsh-rfs-empty' },\n React.createElement(FileTypeIcon, {\n kind: classifyFileType(target ? target.rawPath : ''),\n size: 36,\n className: 'dsh-rfs-emptyIcon',\n }),\n React.createElement('p', { className: 'dsh-rfs-emptyLine' }, message),\n React.createElement('button', { type: 'button', className: 'dsh-rfs-retry', onClick: refresh },\n React.createElement(IconRefreshOutlineRegular, { size: 14 }),\n '重试',\n ),\n ) : null,\n phase === 'ready' && showCode ? React.createElement('div', {\n className: 'dsh-rfs-code',\n 'data-code-preview': true,\n 'data-wrap': wrap,\n },\n React.createElement(CodeBlock, {\n className: 'dsh-rfs-codeblock',\n code: view.rows.join('\\n'),\n lang: language,\n streaming: false,\n lineNumbers: true,\n wrap,\n copyLabel: '复制',\n copiedLabel: '已复制',\n toolbarLabels: {\n codeLabel: '代码',\n wrapLabel: '启用自动换行',\n unwrapLabel: '关闭自动换行',\n },\n }),\n ) : null,\n phase === 'ready' && !showCode ? React.createElement('div', {\n className: 'dsh-rfs-textDocument',\n 'data-textpreview-plain': true,\n },\n React.createElement('pre', { className: 'dsh-rfs-page' },\n view.rows.map(function (row, index) {\n const number = index + 1\n return React.createElement('div', {\n key: number,\n 'data-line': String(number),\n className: line === number ? 'dsh-rfs-line dsh-rfs-lineTarget' : 'dsh-rfs-line',\n }, row, '\\n')\n }),\n ),\n ) : null,\n )\n\n return React.createElement('div', { className: 'dsh-rfs-preview' },\n notice !== null ? React.createElement('p', { className: 'dsh-rfs-notice' },\n React.createElement('span', null, notice),\n ) : null,\n React.createElement('div', { className: 'dsh-rfs-header' },\n React.createElement(PathLabel, {\n path: target ? target.target : address,\n className: 'dsh-rfs-path',\n }),\n language !== null ? React.createElement(Menu, {\n open: menuOpen,\n anchor: React.createElement('button', {\n type: 'button',\n className: 'dsh-rfs-tool dsh-rfs-viewerTool',\n 'aria-label': '打开方式',\n title: plain ? '纯文本' : '代码',\n 'data-document-viewer-menu': true,\n onClick: function () { setMenuOpen(function (v) { return !v }) },\n }, plain ? '纯文本' : '代码'),\n items: [\n { id: 'code', label: '代码' },\n { id: 'plain', label: '纯文本' },\n ],\n selectedId: plain ? 'plain' : 'code',\n onSelect: function (id: string) {\n setPlain(id === 'plain')\n setMenuOpen(false)\n },\n onClose: function () { setMenuOpen(false) },\n align: 'end',\n portal: true,\n dense: true,\n }) : null,\n React.createElement(Tooltip, {\n label: wrap ? '关闭自动换行' : '启用自动换行',\n side: 'bottom',\n delayMs: 500,\n children: React.createElement('button', {\n type: 'button',\n className: 'dsh-rfs-tool',\n 'aria-pressed': wrap,\n 'aria-label': '自动换行',\n onClick: function () { setWrap(function (w) { return !w }) },\n }, wrap ? React.createElement(IconNowrapFillRegular, {}) : React.createElement(IconWrapFillRegular, {})),\n }),\n React.createElement(Tooltip, {\n label: '重新加载',\n side: 'bottom',\n delayMs: 500,\n children: React.createElement('button', {\n type: 'button',\n className: 'dsh-rfs-tool',\n 'aria-label': '重新加载',\n onClick: refresh,\n }, React.createElement(IconRefreshOutlineRegular, {})),\n })\n ),\n body,\n )\n}\n\n// read_remote 工具卡\n\n/** read_remote 工具卡的数据块,字段一律按未知收窄。 */\ninterface ReadRemoteBlock {\n kind?: unknown\n isError?: unknown\n argsRaw?: unknown\n call?: { argsRaw?: unknown } | undefined\n content?: unknown\n meta?: unknown\n}\n\n/** tool.call.toolview 的 owner 面,只取本卡用到的字段。 */\ninterface ToolViewProps {\n block: ReadRemoteBlock\n toolName?: string\n openFile?: (path: string) => void | Promise<void>\n inspect?: () => void\n}\n\n/**\n * 从参数里取远端路径。\n * running 的 argsRaw 在块顶层,settled 的挪到 block.call 上,两处兼容。\n * @param block - 工具卡数据块。\n */\nfunction readRemotePath(block: ReadRemoteBlock): string | undefined {\n const settled = 'kind' in block\n const raw = settled ? block.call?.argsRaw : block.argsRaw\n if (typeof raw !== 'string') return undefined\n try {\n const parsed: unknown = JSON.parse(raw)\n if (parsed !== null && typeof parsed === 'object') {\n const p = (parsed as Record<string, unknown>).file_path\n if (typeof p === 'string' && p !== '') return p\n }\n } catch {\n // 参数尚未就绪时不报错,等下一帧。\n }\n return undefined\n}\n\n/** 取工具结果里的文本,供展开的输入输出卡渲染。 */\nfunction readRemoteText(block: ReadRemoteBlock): string | null {\n const content = Array.isArray(block.content) ? block.content : []\n for (const item of content) {\n if (item !== null && typeof item === 'object') {\n const record = item as { type?: unknown; text?: unknown }\n if (record.type === 'text' && typeof record.text === 'string') return record.text\n }\n }\n return null\n}\n\n/** 取格式化后的调用参数,解析失败时原样返回。 */\nfunction readRemoteInput(block: ReadRemoteBlock): string | null {\n const settled = 'kind' in block\n const raw = settled ? block.call?.argsRaw : block.argsRaw\n if (typeof raw !== 'string' || raw === '') return null\n try {\n return JSON.stringify(JSON.parse(raw), null, 2)\n } catch {\n return raw\n }\n}\n\n/**\n * read_remote 的工具卡。\n *\n * 内置 read 卡按工具名 read 注册,read_remote 没有对应条目而走通用行,卡里没有打开文件的入口,\n * 因此这里接管它,给出可点击的远端路径,经 owner 的 openFile 在右侧栏打开 remote tab。\n * 点击行头展开结果文本,输入与输出仍可回看。\n * @param props - 框架注入的工具卡 owner 面。\n */\nfunction ReadRemoteRow(props: ToolViewProps): React.ReactElement {\n const block = props.block\n const filePath = readRemotePath(block)\n const settled = 'kind' in block\n const failed = settled && block.isError === true\n const [open, setOpen] = React.useState(false)\n const input = readRemoteInput(block)\n const output = settled ? readRemoteText(block) : null\n const expandable = input !== null || output !== null\n const canOpen = filePath !== undefined && props.openFile !== undefined && !failed\n function onOpenFile(event: React.MouseEvent): void {\n event.stopPropagation()\n if (filePath === undefined || props.openFile === undefined) return\n const opened = props.openFile(filePath)\n if (opened instanceof Promise) opened.catch(function (e: unknown) {\n console.error('[remote-file-system] 打开远端文件失败', e)\n })\n }\n function onToggle(): void { setOpen(function (v) { return !v }) }\n // 与内置工具行同源的展开指示箭头。\n const icon = React.createElement('svg', {\n width: 14, height: 14, viewBox: '0 0 16 16', fill: 'none',\n xmlns: 'http://www.w3.org/2000/svg', 'aria-hidden': 'true', strokeWidth: 1,\n }, React.createElement('path', {\n d: 'M12 10L8.70711 6.70711C8.31658 6.31658 7.68342 6.31658 7.29289 6.70711L4 10',\n stroke: 'currentColor',\n }))\n return React.createElement('div', { className: 'dsh-rfs-root' },\n React.createElement('div', {\n className: 'dsh-rfs-row',\n 'data-disclosure-row': true,\n 'data-expandable': expandable ? true : undefined,\n role: expandable ? 'button' : undefined,\n tabIndex: expandable ? 0 : undefined,\n 'aria-expanded': expandable ? open : undefined,\n onClick: expandable ? onToggle : undefined,\n },\n React.createElement('span', { className: 'dsh-rfs-leading' }, icon),\n React.createElement('span', { className: 'dsh-rfs-content' },\n React.createElement('span', { className: 'dsh-rfs-rowtitle' }, '工具调用'),\n React.createElement('span', { className: 'dsh-rfs-sep', 'data-shimmer-decoration': 'true', 'aria-hidden': true }),\n React.createElement('span', { className: 'dsh-rfs-summary' },\n React.createElement('span', { className: 'dsh-rfs-summarytext' }, filePath !== undefined ? 'read_remote · ' : 'read_remote'),\n filePath !== undefined\n ? (canOpen\n ? React.createElement('button', { type: 'button', className: 'dsh-rfs-summarylink', onClick: onOpenFile }, filePath)\n : React.createElement('span', { className: 'dsh-rfs-summarylink' }, filePath))\n : null,\n ),\n ),\n ),\n expandable && open ? React.createElement('div', { className: 'dsh-rfs-iocard' },\n input !== null ? React.createElement('div', { className: 'dsh-rfs-iosection' },\n React.createElement('span', { className: 'dsh-rfs-iolabel' }, '输入'),\n React.createElement('span', { className: 'dsh-rfs-iotext' }, input),\n ) : null,\n input !== null && output !== null ? React.createElement('span', { className: 'dsh-rfs-iodivider', 'aria-hidden': true }) : null,\n output !== null ? React.createElement('div', { className: 'dsh-rfs-iosection' },\n React.createElement('span', { className: 'dsh-rfs-iolabel' }, '输出'),\n React.createElement('span', { className: 'dsh-rfs-iotext', ...(failed ? { 'data-error': true } : {}) }, output),\n ) : null,\n ) : null,\n )\n}\n\n// tab 标题\n\n/**\n * tab 条上的标题:文件类型图标加文件名,与内置文档预览的 TextTitle 同构。\n * @param props - 框架注入的 tab 信息钩子。\n */\nfunction RemoteFileTitle(props: TabTitleProps): React.ReactElement {\n const info = props.useTabInfo()\n return React.createElement(React.Fragment, null,\n React.createElement(FileTypeIcon, {\n kind: classifyFileType(info.tab.title),\n size: 16,\n className: 'dsh-rfs-titleIcon',\n }),\n info.tab.title,\n )\n}\n\n// 插件入口\n\n/** 插件名,与 cordis.patch.yml 的 name 一致。 */\nexport const name = 'dsh-remote-file-system'\n\n/** 依赖的客户端服务:右侧栏 tab 注册表与槽位。 */\nexport const inject: string[] = ['slots', 'sidebarRightTabs', 'sidebarRight']\n\n/**\n * 客户端 apply:注册 remote tab 类型与它的正文。\n *\n * 类型以 extension 优先级压过内置 text 的 fallback,但只认领能解析出远程别名的会话地址,\n * 本地文件仍由内置查看器处理。\n * @param ctx - 客户端插件上下文。\n */\nexport function apply(ctx: ClientContext): void {\n const slots = ctx.get('slots') as SlotsService | undefined\n if (!slots) return\n const tabs = ctx.get('sidebarRightTabs') as TabsService | undefined\n if (!tabs) throw new Error('[remote-file-system] 缺少 sidebarRightTabs 服务,请确认 dsh-client-ui-sidebar-right 已加载')\n const right = ctx.get('sidebarRight') as SidebarRightService | undefined\n if (!right) throw new Error('[remote-file-system] 缺少 sidebarRight 服务,请确认 dsh-client-ui-sidebar-right 已加载')\n\n ctx.effect(function () {\n ensureStyle()\n return function () { removeStyle() }\n })\n\n ctx.effect(function () {\n return tabs.register({\n id: TYPE_ID,\n kind: KIND,\n priority: 'extension',\n patterns: ['dsh-resource://file/**'],\n canOpen: function (address) { return parseRemoteAddress(address) !== null },\n title: function (address) {\n const parsed = parseRemoteAddress(address)\n return parsed ? basenameOf(parsed.rawPath) : address\n },\n keepMounted: true,\n })\n })\n\n slots.inject('sidebar.right.pane.tab', function () {\n return slots.register({ name: 'sidebar.right.pane.tab', key: TYPE_ID }, RemoteFileBody)\n })\n\n slots.inject('sidebar.right.pane.tab.title', function () {\n return slots.register({ name: 'sidebar.right.pane.tab.title', key: TYPE_ID }, RemoteFileTitle)\n })\n\n // read_remote 的工具卡原本没有打开文件的入口,接管它并给出可点击路径。\n slots.inject('tool.call.toolview', function () {\n return slots.register({ name: 'tool.call.toolview', key: 'read_remote', priority: 0 }, ReadRemoteRow)\n })\n}\n\nconst plugin = { name, inject, apply }\nexport default plugin\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["name", "body"]
|
|
7
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -193,13 +193,14 @@ var RemoteFileSystem = class {
|
|
|
193
193
|
if (expected !== void 0 && expected.kind !== "createIfAbsent") {
|
|
194
194
|
throw new FsError2("write_remote 只允许新建,仅支持 createIfAbsent 意图", "FS_NOT_OBSERVED");
|
|
195
195
|
}
|
|
196
|
-
const env = { RM_PATH: b64(rawPath)
|
|
196
|
+
const env = { RM_PATH: b64(rawPath) };
|
|
197
|
+
const data = b64(content);
|
|
197
198
|
const action = [
|
|
198
199
|
PATH_PROLOGUE,
|
|
199
200
|
DIE_HELPER,
|
|
200
201
|
'TMP=$(mktemp "$P.XXXXXXXX.tmp" 2>/dev/null)',
|
|
201
202
|
'if [ -z "$TMP" ]; then die FS_IO_ERROR 无法创建临时文件,目录可能不可写; exit 0; fi',
|
|
202
|
-
|
|
203
|
+
`printf %s '${data}' | base64 -d > "$TMP" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR 写入临时文件失败; exit 0; }`,
|
|
203
204
|
'if [ -e "$P" ] || [ -L "$P" ]; then rm -f "$TMP"; die FS_NOT_OBSERVED 文件已存在,write_remote 只允许新建; exit 0; fi',
|
|
204
205
|
'mv -f "$TMP" "$P" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR mv 发布失败; exit 0; }',
|
|
205
206
|
"echo OK"
|
|
@@ -210,13 +211,14 @@ var RemoteFileSystem = class {
|
|
|
210
211
|
/** 编辑后写回,整块替换。 */
|
|
211
212
|
async writeReplaced(target, content) {
|
|
212
213
|
const { alias, rawPath } = splitAliasPath(target.displayPath);
|
|
213
|
-
const env = { RM_PATH: b64(rawPath)
|
|
214
|
+
const env = { RM_PATH: b64(rawPath) };
|
|
215
|
+
const data = b64(content);
|
|
214
216
|
const action = [
|
|
215
217
|
PATH_PROLOGUE,
|
|
216
218
|
DIE_HELPER,
|
|
217
219
|
'TMP=$(mktemp "$P.XXXXXXXX.tmp" 2>/dev/null)',
|
|
218
220
|
'if [ -z "$TMP" ]; then die FS_IO_ERROR 无法创建临时文件; exit 0; fi',
|
|
219
|
-
|
|
221
|
+
`printf %s '${data}' | base64 -d > "$TMP" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR 写入临时文件失败; exit 0; }`,
|
|
220
222
|
'if [ ! -e "$P" ]; then rm -f "$TMP"; die FS_NOT_FOUND 文件已不存在; exit 0; fi',
|
|
221
223
|
'mv -f "$TMP" "$P" 2>/dev/null || { rm -f "$TMP"; die FS_IO_ERROR mv 替换失败; exit 0; }',
|
|
222
224
|
"echo OK"
|
|
@@ -969,6 +971,155 @@ function createEditRemoteTool(fs) {
|
|
|
969
971
|
});
|
|
970
972
|
}
|
|
971
973
|
|
|
974
|
+
// src/rpc-channel.ts
|
|
975
|
+
var SEGMENT = /^[A-Za-z0-9_$.-]+$/;
|
|
976
|
+
var MAX_BODY_BYTES = 300 * 1024 * 1024;
|
|
977
|
+
var JSON_MIME = "application/json";
|
|
978
|
+
var PayloadTooLargeError = class extends Error {
|
|
979
|
+
};
|
|
980
|
+
function mountRpcChannel(ctx, channel, handler) {
|
|
981
|
+
ctx.inject(["connection", "webServer"], (raw) => {
|
|
982
|
+
const scope = raw;
|
|
983
|
+
scope.effect(
|
|
984
|
+
() => scope.webServer.register({
|
|
985
|
+
kind: "prefix",
|
|
986
|
+
path: channel,
|
|
987
|
+
handler: (req, res) => serve(scope.connection, channel, handler, req, res)
|
|
988
|
+
}),
|
|
989
|
+
channel + ": rpc channel"
|
|
990
|
+
);
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
async function serve(connection, channel, handler, req, res) {
|
|
994
|
+
const rejection = connection.requestRejection(req);
|
|
995
|
+
if (rejection !== void 0) {
|
|
996
|
+
res.writeHead(rejection);
|
|
997
|
+
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (req.method !== "POST") return missing(res);
|
|
1001
|
+
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
1002
|
+
const prefix = channel + "/";
|
|
1003
|
+
if (!pathname.startsWith(prefix)) return missing(res);
|
|
1004
|
+
const endpoint = pathname.slice(prefix.length);
|
|
1005
|
+
if (!endpoint.split("/").every((part) => part !== "" && part !== "." && part !== ".." && SEGMENT.test(part))) return missing(res);
|
|
1006
|
+
if (!isJsonContentType(req)) {
|
|
1007
|
+
res.writeHead(415);
|
|
1008
|
+
res.end("unsupported media type");
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const declared = Number(req.headers["content-length"]);
|
|
1012
|
+
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) return tooLarge(req, res);
|
|
1013
|
+
let raw;
|
|
1014
|
+
try {
|
|
1015
|
+
raw = await readBody(req);
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
if (error instanceof PayloadTooLargeError) return tooLarge(req, res);
|
|
1018
|
+
res.writeHead(400);
|
|
1019
|
+
res.end("body read failed");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
let message;
|
|
1023
|
+
try {
|
|
1024
|
+
message = JSON.parse(raw);
|
|
1025
|
+
} catch {
|
|
1026
|
+
res.writeHead(400);
|
|
1027
|
+
res.end("body is not JSON");
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const rpcId = typeof message.rpcId === "string" ? message.rpcId : "invalid-request";
|
|
1031
|
+
if (message.type !== "client-request" || typeof message.method !== "string") {
|
|
1032
|
+
return reply(res, rpcId, { ok: false, error: { code: "gateway/bad-request", message: "invalid client-request message", details: {} } });
|
|
1033
|
+
}
|
|
1034
|
+
if (message.method !== endpoint) {
|
|
1035
|
+
return reply(res, rpcId, {
|
|
1036
|
+
ok: false,
|
|
1037
|
+
error: { code: "gateway/bad-request", message: "method " + JSON.stringify(message.method) + " does not match endpoint " + JSON.stringify(endpoint), details: {} }
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
const controller = new AbortController();
|
|
1041
|
+
const onClose = () => {
|
|
1042
|
+
controller.abort(new Error("client disconnected"));
|
|
1043
|
+
};
|
|
1044
|
+
res.on("close", onClose);
|
|
1045
|
+
let result;
|
|
1046
|
+
try {
|
|
1047
|
+
result = await handler(endpoint, message.payload, controller.signal);
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
result = { ok: false, error: { code: "internal", message: error instanceof Error ? error.message : String(error), details: {} } };
|
|
1050
|
+
} finally {
|
|
1051
|
+
res.off("close", onClose);
|
|
1052
|
+
}
|
|
1053
|
+
reply(res, rpcId, result);
|
|
1054
|
+
}
|
|
1055
|
+
function missing(res) {
|
|
1056
|
+
res.writeHead(404);
|
|
1057
|
+
res.end("not found");
|
|
1058
|
+
}
|
|
1059
|
+
function reply(res, rpcId, result) {
|
|
1060
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
1061
|
+
res.end(JSON.stringify({ type: "server-response", rpcId, result }));
|
|
1062
|
+
}
|
|
1063
|
+
function tooLarge(req, res) {
|
|
1064
|
+
res.writeHead(413, { connection: "close" });
|
|
1065
|
+
res.end("payload too large");
|
|
1066
|
+
req.destroy();
|
|
1067
|
+
}
|
|
1068
|
+
function isJsonContentType(req) {
|
|
1069
|
+
const header = req.headers["content-type"];
|
|
1070
|
+
if (typeof header !== "string") return false;
|
|
1071
|
+
const mime = header.split(";", 1)[0];
|
|
1072
|
+
return mime !== void 0 && mime.trim().toLowerCase() === JSON_MIME;
|
|
1073
|
+
}
|
|
1074
|
+
async function readBody(req) {
|
|
1075
|
+
const chunks = [];
|
|
1076
|
+
let size = 0;
|
|
1077
|
+
for await (const chunk of req) {
|
|
1078
|
+
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
1079
|
+
size += buffer.length;
|
|
1080
|
+
if (size > MAX_BODY_BYTES) throw new PayloadTooLargeError("rpc channel: request body exceeds limit");
|
|
1081
|
+
chunks.push(buffer);
|
|
1082
|
+
}
|
|
1083
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/rpc.ts
|
|
1087
|
+
var MAX_PREVIEW_CHARS = 2 * 1024 * 1024;
|
|
1088
|
+
var BadRequestError = class extends Error {
|
|
1089
|
+
};
|
|
1090
|
+
function asRecord(value) {
|
|
1091
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
1092
|
+
}
|
|
1093
|
+
function readString(record, key) {
|
|
1094
|
+
const value = record[key];
|
|
1095
|
+
return typeof value === "string" ? value : void 0;
|
|
1096
|
+
}
|
|
1097
|
+
async function readRemote(fs, payload) {
|
|
1098
|
+
const target = readString(asRecord(payload), "target");
|
|
1099
|
+
if (target === void 0 || target.trim() === "") throw new BadRequestError("read 缺少 target");
|
|
1100
|
+
const resolved = await fs.resolve(target);
|
|
1101
|
+
const text = await fs.readText(resolved);
|
|
1102
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
1103
|
+
if (text.length > MAX_PREVIEW_CHARS) {
|
|
1104
|
+
return { path: resolved.displayPath, text: text.slice(0, MAX_PREVIEW_CHARS), bytes, truncated: true };
|
|
1105
|
+
}
|
|
1106
|
+
return { path: resolved.displayPath, text, bytes, truncated: false };
|
|
1107
|
+
}
|
|
1108
|
+
async function handleRemoteRpc(fs, endpoint, payload) {
|
|
1109
|
+
try {
|
|
1110
|
+
switch (endpoint) {
|
|
1111
|
+
case "read":
|
|
1112
|
+
return { ok: true, value: await readRemote(fs, payload) };
|
|
1113
|
+
default:
|
|
1114
|
+
return { ok: false, error: { code: "bad-request", message: "未知端点: " + endpoint, details: {} } };
|
|
1115
|
+
}
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1118
|
+
if (e instanceof BadRequestError) return { ok: false, error: { code: "bad-request", message, details: {} } };
|
|
1119
|
+
return { ok: false, error: { code: "internal", message, details: {} } };
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
972
1123
|
// src/index.ts
|
|
973
1124
|
var name = "dsh-remote-file-system";
|
|
974
1125
|
var inject = ["tools", "systemPrompt"];
|
|
@@ -993,6 +1144,7 @@ function apply(ctx, config = {}) {
|
|
|
993
1144
|
ctx.tools.register(createReadRemoteTool(fs));
|
|
994
1145
|
ctx.tools.register(createWriteRemoteTool(fs));
|
|
995
1146
|
ctx.tools.register(createEditRemoteTool(fs));
|
|
1147
|
+
mountRpcChannel(ctx, "/remote-file-system", (endpoint, payload) => handleRemoteRpc(fs, endpoint, payload));
|
|
996
1148
|
}
|
|
997
1149
|
export {
|
|
998
1150
|
apply,
|