dsh-coding-sidebar 1.0.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 +524 -0
- package/cordis.patch.yml +49 -0
- package/lib/client-editor.js +48229 -0
- package/lib/client-mermaid.js +200745 -0
- package/lib/client-registry.js +21867 -0
- package/lib/client-terminal.js +9361 -0
- package/lib/client.js +21867 -0
- package/lib/index.js +4556 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-opens.d.ts +65 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/client/BrowserView.d.ts +39 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +13 -0
- package/lib/types/client/FileTree.d.ts +38 -0
- package/lib/types/client/FreeWindow.d.ts +29 -0
- package/lib/types/client/GitView.d.ts +10 -0
- package/lib/types/client/MarkdownHtml.d.ts +32 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/SideChatView.d.ts +16 -0
- package/lib/types/client/Sidebar.d.ts +6 -0
- package/lib/types/client/SubagentView.d.ts +16 -0
- package/lib/types/client/TabBar.d.ts +54 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +24 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +275 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +17 -0
- package/lib/types/client/browser.d.ts +60 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chunk-loader.d.ts +131 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +59 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +25 -0
- package/lib/types/client/intercept.d.ts +46 -0
- package/lib/types/client/lang.d.ts +14 -0
- package/lib/types/client/layout-push.d.ts +15 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales-ar.d.ts +18 -0
- package/lib/types/client/locales-de.d.ts +4 -0
- package/lib/types/client/locales-fr.d.ts +10 -0
- package/lib/types/client/locales-hi.d.ts +17 -0
- package/lib/types/client/locales-id.d.ts +15 -0
- package/lib/types/client/locales-it.d.ts +8 -0
- package/lib/types/client/locales-ja.d.ts +17 -0
- package/lib/types/client/locales-ko.d.ts +9 -0
- package/lib/types/client/locales-nl.d.ts +16 -0
- package/lib/types/client/locales-pl.d.ts +19 -0
- package/lib/types/client/locales-pt.d.ts +1 -0
- package/lib/types/client/locales-ru.d.ts +13 -0
- package/lib/types/client/locales-sv.d.ts +1 -0
- package/lib/types/client/locales-th.d.ts +17 -0
- package/lib/types/client/locales-tr.d.ts +17 -0
- package/lib/types/client/locales-vi.d.ts +17 -0
- package/lib/types/client/locales-zh-HK.d.ts +32 -0
- package/lib/types/client/locales-zh-MO.d.ts +32 -0
- package/lib/types/client/locales-zh-TW.d.ts +32 -0
- package/lib/types/client/locales.d.ts +452 -0
- package/lib/types/client/markdown-html.d.ts +96 -0
- package/lib/types/client/markdown-images.d.ts +45 -0
- package/lib/types/client/markdown-labels.d.ts +38 -0
- package/lib/types/client/md-toc.d.ts +4 -0
- package/lib/types/client/mermaid-blocks.d.ts +47 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +85 -0
- package/lib/types/client/paths.d.ts +43 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/pinned.d.ts +86 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +29 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +439 -0
- package/lib/types/client/settings-nav-icon.d.ts +19 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/sidechat-transcript.d.ts +103 -0
- package/lib/types/client/split-pane.d.ts +36 -0
- package/lib/types/client/state.d.ts +432 -0
- package/lib/types/client/subagent-detect.d.ts +54 -0
- package/lib/types/client/subagent-jobs.d.ts +63 -0
- package/lib/types/client/tab-content-memo.d.ts +40 -0
- package/lib/types/client/terminal-font.d.ts +78 -0
- package/lib/types/client/terminal-links.d.ts +119 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/config.d.ts +64 -0
- package/lib/types/context-types.d.ts +605 -0
- package/lib/types/fs-operations.d.ts +28 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +124 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/jobs-routes.d.ts +45 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/path-security.d.ts +20 -0
- package/lib/types/prefs-shared.d.ts +236 -0
- package/lib/types/pty-deps.d.ts +78 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/sidechat-core.d.ts +172 -0
- package/lib/types/sidechat-routes.d.ts +30 -0
- package/lib/types/subagent-activity.d.ts +44 -0
- package/lib/types/subagent-live-route.d.ts +44 -0
- package/lib/types/tools.d.ts +34 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +172 -0
- package/scripts/install.ps1 +306 -0
- package/scripts/install.sh +287 -0
- package/src/agent-opens.ts +265 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/client/BrowserView.tsx +289 -0
- package/src/client/DiffTab.tsx +114 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +558 -0
- package/src/client/FileTree.tsx +669 -0
- package/src/client/FreeWindow.tsx +337 -0
- package/src/client/GitView.tsx +717 -0
- package/src/client/MarkdownHtml.tsx +296 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +960 -0
- package/src/client/SideCardSection.tsx +1163 -0
- package/src/client/SideChatView.module.css +437 -0
- package/src/client/SideChatView.tsx +645 -0
- package/src/client/Sidebar.tsx +1370 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +883 -0
- package/src/client/TabBar.tsx +365 -0
- package/src/client/TerminalView.tsx +424 -0
- package/src/client/TextEditor.tsx +468 -0
- package/src/client/TreePanel.tsx +275 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +357 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +61 -0
- package/src/client/browser.ts +167 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +340 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chunk-loader.ts +358 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +152 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +427 -0
- package/src/client/intercept.tsx +188 -0
- package/src/client/lang.ts +225 -0
- package/src/client/layout-push.ts +24 -0
- package/src/client/layout.css +90 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales-ar.ts +368 -0
- package/src/client/locales-de.ts +353 -0
- package/src/client/locales-fr.ts +360 -0
- package/src/client/locales-hi.ts +367 -0
- package/src/client/locales-id.ts +365 -0
- package/src/client/locales-it.ts +358 -0
- package/src/client/locales-ja.ts +367 -0
- package/src/client/locales-ko.ts +359 -0
- package/src/client/locales-nl.ts +365 -0
- package/src/client/locales-pl.ts +369 -0
- package/src/client/locales-pt.ts +350 -0
- package/src/client/locales-ru.ts +365 -0
- package/src/client/locales-sv.ts +350 -0
- package/src/client/locales-th.ts +367 -0
- package/src/client/locales-tr.ts +367 -0
- package/src/client/locales-vi.ts +367 -0
- package/src/client/locales-zh-HK.ts +382 -0
- package/src/client/locales-zh-MO.ts +382 -0
- package/src/client/locales-zh-TW.ts +382 -0
- package/src/client/locales.ts +894 -0
- package/src/client/markdown-html.ts +331 -0
- package/src/client/markdown-images.ts +137 -0
- package/src/client/markdown-labels.tsx +52 -0
- package/src/client/md-toc.tsx +127 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +406 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +143 -0
- package/src/client/paths.ts +63 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/pinned.ts +183 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +96 -0
- package/src/client/plugins-viewers.ts +30 -0
- package/src/client/prefs.ts +210 -0
- package/src/client/produced-files.ts +120 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +859 -0
- package/src/client/settings-nav-icon.ts +45 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +2788 -0
- package/src/client/sidechat-transcript.ts +321 -0
- package/src/client/split-pane.tsx +331 -0
- package/src/client/state.ts +1497 -0
- package/src/client/subagent-detect.ts +137 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/tab-content-memo.ts +55 -0
- package/src/client/terminal-font.ts +224 -0
- package/src/client/terminal-links.ts +194 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/upload.ts +187 -0
- package/src/client/wco.ts +120 -0
- package/src/config.ts +154 -0
- package/src/context-types.ts +611 -0
- package/src/fs-operations.ts +95 -0
- package/src/fs-search.ts +113 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +446 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1291 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/open-external.ts +90 -0
- package/src/path-security.ts +75 -0
- package/src/prefs-shared.ts +280 -0
- package/src/pty-deps.ts +240 -0
- package/src/pty-manager.ts +376 -0
- package/src/sidechat-core.ts +468 -0
- package/src/sidechat-routes.ts +344 -0
- package/src/subagent-activity.ts +91 -0
- package/src/subagent-live-route.ts +93 -0
- package/src/tools.ts +482 -0
- package/src/trust-fence.ts +84 -0
- package/src/wire.ts +100 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# dsh-coding-sidebar 一键安装脚本(官方 CLI 方式,macOS / Linux / Windows Git Bash)
|
|
4
|
+
#
|
|
5
|
+
# 通过 DSH 官方插件命令安装 npm 包并自动挂载:
|
|
6
|
+
# dsh plugin --profile web add dsh-coding-sidebar@<version>
|
|
7
|
+
#
|
|
8
|
+
# 包内声明了 dsh.bundle.patch(cordis.patch.yml):CLI 的 bundle 协调会把它
|
|
9
|
+
# 自动加进 profile 的 dsh.profile.bundles,下次启动即挂载——无需手动写
|
|
10
|
+
# cordis.patch.yml 挂载行。符合仓库硬约束:不修改 DSH 源码,插件永远作为
|
|
11
|
+
# 独立包被 profile 引用。
|
|
12
|
+
#
|
|
13
|
+
# 用法:
|
|
14
|
+
# bash scripts/install.sh [版本] [--restart] [--dry-run]
|
|
15
|
+
# bash scripts/install.sh --repair [--profile <名>] [--dry-run]
|
|
16
|
+
#
|
|
17
|
+
# 版本 npm 版本号/范围,缺省为 latest(自动解析为 ^<最新>)。
|
|
18
|
+
# 示例:0.10.2、^0.10.2、~0.10.2、latest
|
|
19
|
+
# --repair 修复模式:不重装插件,只确保 profile 的 pnpm-workspace.yaml
|
|
20
|
+
# 放行 node-pty 构建脚本,然后重跑 pnpm install + pnpm rebuild
|
|
21
|
+
# node-pty(终端提示「node-pty 加载失败」时用它,见 issue #140)。
|
|
22
|
+
# --profile 目标 profile 名(缺省 web);安装与修复模式均适用。
|
|
23
|
+
# --restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅打印提示)。
|
|
24
|
+
# 注意:重启会断开当前 DSH 页面会话,默认不自动重启。
|
|
25
|
+
# --dry-run 只打印将要执行的操作,不写任何文件。
|
|
26
|
+
# -h/--help 打印本帮助。
|
|
27
|
+
#
|
|
28
|
+
# 环境(均可省略,脚本会自动探测):
|
|
29
|
+
# DSH_HOME 默认 ~/.dsh(Windows Git Bash 下回退 $USERPROFILE/.dsh)
|
|
30
|
+
# REGISTRY 默认 https://registry.npmjs.org(发布源;装依赖仍走 pnpm 配置)
|
|
31
|
+
# DSH_CMD 默认优先用 PATH 上的 `dsh`,缺省回退 npx -y --package @deepseek-ai/dsh
|
|
32
|
+
#
|
|
33
|
+
# 说明:
|
|
34
|
+
# - pnpm 11 的 strict-dep-builds 会拦截 node-pty/protobufjs 的构建脚本并使
|
|
35
|
+
# `dsh plugin add` 非零退出(bundle 协调因此被跳过)。脚本会先把这两个
|
|
36
|
+
# 构建许可写进 profile 的 pnpm-workspace.yaml(幂等),保证 CLI 一步成功。
|
|
37
|
+
# - pnpm 11 的 minimumReleaseAge 会拒绝发布 <24h 的新版本。脚本会预写
|
|
38
|
+
# minimumReleaseAgeExclude(幂等),放行本插件,避免"重跑一次才成功"。
|
|
39
|
+
# - 老版本(<0.10.2)用手动挂载行,bundle 通道激活后需移除,否则双挂载
|
|
40
|
+
# (Node 半挂两次、页面两个侧边栏)。脚本会幂等移除 better-sidebar 挂载行。
|
|
41
|
+
# - 回滚:dsh plugin --profile web remove dsh-coding-sidebar,或把 profile 依赖
|
|
42
|
+
# 改回 "dsh-coding-sidebar": "link:<路径>" 再 pnpm install。
|
|
43
|
+
# =============================================================================
|
|
44
|
+
set -euo pipefail
|
|
45
|
+
|
|
46
|
+
# 帮助请求优先处理
|
|
47
|
+
for arg in "$@"; do
|
|
48
|
+
if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
|
|
49
|
+
cat <<'EOF'
|
|
50
|
+
dsh-coding-sidebar 一键安装 / 依赖修复脚本
|
|
51
|
+
|
|
52
|
+
用法:
|
|
53
|
+
bash scripts/install.sh [版本] [--restart] [--dry-run] [--profile <名>]
|
|
54
|
+
bash scripts/install.sh --repair [--profile <名>] [--dry-run]
|
|
55
|
+
|
|
56
|
+
版本 npm 版本号/范围,缺省 latest(自动解析为最新)。示例:0.10.2、^0.10.2、latest
|
|
57
|
+
--repair 修复模式:确保 profile 放行 node-pty 构建脚本并重装 node-pty(终端提示依赖加载失败时用)
|
|
58
|
+
--profile 目标 profile 名(缺省 web)
|
|
59
|
+
--restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅提示)
|
|
60
|
+
--dry-run 只打印将要执行的操作,不写任何文件
|
|
61
|
+
|
|
62
|
+
环境变量(可省略):DSH_HOME(默认 ~/.dsh)、REGISTRY(npm 源)、DSH_CMD(dsh 命令)
|
|
63
|
+
EOF
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
DSH_HOME="${DSH_HOME:-${HOME:-${USERPROFILE:-}}/.dsh}"
|
|
69
|
+
REGISTRY="${REGISTRY:-https://registry.npmjs.org}"
|
|
70
|
+
PKG="dsh-coding-sidebar"
|
|
71
|
+
DSH_CMD="${DSH_CMD:-dsh}"
|
|
72
|
+
|
|
73
|
+
RESTART=false
|
|
74
|
+
DRY_RUN=false
|
|
75
|
+
REPAIR=false
|
|
76
|
+
VERSION_SPEC=""
|
|
77
|
+
PROFILE_NAME="web"
|
|
78
|
+
while [ $# -gt 0 ]; do
|
|
79
|
+
case "$1" in
|
|
80
|
+
--restart) RESTART=true ;;
|
|
81
|
+
--dry-run) DRY_RUN=true ;;
|
|
82
|
+
--repair) REPAIR=true ;;
|
|
83
|
+
--profile)
|
|
84
|
+
if [ $# -lt 2 ]; then echo "--profile 需要一个 profile 名(如 web)" >&2; exit 2; fi
|
|
85
|
+
PROFILE_NAME="$2"; shift ;;
|
|
86
|
+
-h|--help) : ;; # 已在上面处理
|
|
87
|
+
-*) echo "未知参数: ${1}(用 -h 查看用法)" >&2; exit 2 ;;
|
|
88
|
+
*) VERSION_SPEC="$1" ;;
|
|
89
|
+
esac
|
|
90
|
+
shift
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE_NAME"
|
|
94
|
+
WS_YML="$PROFILE_DIR/pnpm-workspace.yaml"
|
|
95
|
+
PATCH_YML="$PROFILE_DIR/cordis.patch.yml"
|
|
96
|
+
|
|
97
|
+
say() { printf '\033[32m[install]\033[0m %s\n' "$*"; }
|
|
98
|
+
warn() { printf '\033[33m[warn]\033[0m %s\n' "$*" >&2; }
|
|
99
|
+
die() { printf '\033[31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
|
|
100
|
+
|
|
101
|
+
# 步骤 1(安装与修复共用):预写 workspace 设置(幂等),保证 pnpm 不拦截
|
|
102
|
+
# node-pty/protobufjs 构建脚本、放行本插件新版本
|
|
103
|
+
ensure_workspace_settings() {
|
|
104
|
+
WS_RESULT="$(node -e '
|
|
105
|
+
const fs = require("fs");
|
|
106
|
+
const p = process.argv[1];
|
|
107
|
+
let t = fs.readFileSync(p, "utf8");
|
|
108
|
+
const before = t;
|
|
109
|
+
// allowBuilds:把 node-pty/protobufjs 归位为 true
|
|
110
|
+
t = t.replace(/^(\s*)(node-pty|protobufjs):.*$/gm, "$1$2: true");
|
|
111
|
+
if (!/^\s*allowBuilds:\s*$/m.test(t)) {
|
|
112
|
+
t += "\nallowBuilds:\n node-pty: true\n protobufjs: true\n";
|
|
113
|
+
} else {
|
|
114
|
+
for (const k of ["node-pty", "protobufjs"]) {
|
|
115
|
+
if (!new RegExp("^\\s*" + k + ":\\s*true\\s*$", "m").test(t)) {
|
|
116
|
+
t = t.replace(/^(\s*allowBuilds:\s*)$/m, "$1\n " + k + ": true");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// minimumReleaseAgeExclude:放行本插件(版本无关),避免 <24h 新版本被拒
|
|
121
|
+
if (!/^\s*-\s+dsh-coding-sidebar\s*$/m.test(t)) {
|
|
122
|
+
if (/^\s*minimumReleaseAgeExclude:\s*$/m.test(t)) {
|
|
123
|
+
t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-coding-sidebar");
|
|
124
|
+
} else {
|
|
125
|
+
t += "\nminimumReleaseAgeExclude:\n - dsh-coding-sidebar\n";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (t !== before) fs.writeFileSync(p, t);
|
|
129
|
+
console.log(t === before ? "unchanged" : "updated");
|
|
130
|
+
' "$WS_YML")"
|
|
131
|
+
[ "$WS_RESULT" = "updated" ] \
|
|
132
|
+
&& say "已确保 ${WS_YML}:allowBuilds(node-pty/protobufjs: true)+ minimumReleaseAgeExclude(${PKG})" \
|
|
133
|
+
|| say "workspace 设置已就绪,跳过"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# 解析用户给的版本 -> CLI 要用的 npm spec("x.y.z" / "^x.y.z" / latest)
|
|
137
|
+
resolve_spec() {
|
|
138
|
+
local given="${1:-latest}"
|
|
139
|
+
case "$given" in
|
|
140
|
+
latest)
|
|
141
|
+
local v=""
|
|
142
|
+
if command -v npm >/dev/null 2>&1; then
|
|
143
|
+
v="$(npm view "$PKG" version --registry="$REGISTRY" 2>/dev/null)" || v=""
|
|
144
|
+
fi
|
|
145
|
+
if [ -z "$v" ] && command -v pnpm >/dev/null 2>&1; then
|
|
146
|
+
v="$(pnpm view "$PKG" version --registry="$REGISTRY" 2>/dev/null)" || v=""
|
|
147
|
+
fi
|
|
148
|
+
if [ -n "$v" ]; then
|
|
149
|
+
printf '%s' "$v"
|
|
150
|
+
else
|
|
151
|
+
warn "无法联网解析最新版本(npm/pnpm 查询失败),回退为 latest,由 pnpm 直接解析。"
|
|
152
|
+
warn "若已知版本号,可显式传入:bash scripts/install.sh 0.10.2"
|
|
153
|
+
printf 'latest'
|
|
154
|
+
fi
|
|
155
|
+
;;
|
|
156
|
+
*) printf '%s' "$given" ;;
|
|
157
|
+
esac
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# 组装 dsh CLI 调用:优先 PATH 上的 dsh,缺省 npx 拉官方包
|
|
161
|
+
dsh_cli() {
|
|
162
|
+
if command -v "$DSH_CMD" >/dev/null 2>&1; then
|
|
163
|
+
printf '%s' "$DSH_CMD"
|
|
164
|
+
elif command -v npx >/dev/null 2>&1; then
|
|
165
|
+
printf 'npx -y --package @deepseek-ai/dsh dsh'
|
|
166
|
+
else
|
|
167
|
+
die "未找到 dsh 或 npx。请先安装 DSH(并确保 Node/npm 可用),或用 DSH_CMD 指定 dsh 路径。"
|
|
168
|
+
fi
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# 前置校验
|
|
172
|
+
command -v node >/dev/null 2>&1 || die "未找到 node(DSH 运行需要 Node.js ≥ 20),请先安装 Node.js 并加入 PATH。"
|
|
173
|
+
|
|
174
|
+
[ -d "$PROFILE_DIR" ] || die "找不到 profile 目录:${PROFILE_DIR}(请先安装并运行过一次 dsh web)"
|
|
175
|
+
[ -f "$WS_YML" ] || die "找不到 ${WS_YML}(请先初始化 ${PROFILE_NAME} profile)"
|
|
176
|
+
|
|
177
|
+
# ── 修复模式(issue #140):不重装插件,只修复 node-pty 依赖 ────────────
|
|
178
|
+
# 终端提示「node-pty 加载失败」时运行:确保 allowBuilds 后重跑
|
|
179
|
+
# pnpm install + pnpm rebuild node-pty(重放被 pnpm 11 拦截的构建脚本)。
|
|
180
|
+
if [ "$REPAIR" = true ]; then
|
|
181
|
+
if [ "$DRY_RUN" = true ]; then
|
|
182
|
+
say "[dry-run] 修复:确保 $WS_YML 含 allowBuilds(node-pty: true)"
|
|
183
|
+
say "[dry-run] 修复:cd $PROFILE_DIR && pnpm install && pnpm rebuild node-pty"
|
|
184
|
+
exit 0
|
|
185
|
+
fi
|
|
186
|
+
say "修复模式:重装 node-pty(profile: ${PROFILE_NAME},${PROFILE_DIR})..."
|
|
187
|
+
ensure_workspace_settings
|
|
188
|
+
if ! (cd "$PROFILE_DIR" && pnpm install && pnpm rebuild node-pty); then
|
|
189
|
+
die "修复失败:pnpm install / pnpm rebuild node-pty 非零退出。请确认 pnpm 在 PATH 上、网络可用,然后重试。"
|
|
190
|
+
fi
|
|
191
|
+
say "修复完成:node-pty 已重装(与 DSH 核心保持同一版本)。请重启 DSH 后重试终端。"
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
SPEC="$(resolve_spec "$VERSION_SPEC")"
|
|
196
|
+
CLI="$(dsh_cli)"
|
|
197
|
+
say "目标:$CLI plugin --profile $PROFILE_NAME add $PKG@${SPEC}(profile: ${PROFILE_DIR})"
|
|
198
|
+
|
|
199
|
+
if [ "$DRY_RUN" = true ]; then
|
|
200
|
+
say "[dry-run] 步骤 1:确保 $WS_YML 含 allowBuilds(node-pty/protobufjs: true)与 minimumReleaseAgeExclude(${PKG})"
|
|
201
|
+
say "[dry-run] 步骤 2:执行 $CLI plugin --profile $PROFILE_NAME add $PKG@${SPEC}(安装 + bundle 自动注册)"
|
|
202
|
+
say "[dry-run] 步骤 3:校验 dsh.profile.bundles 含 $PKG"
|
|
203
|
+
say "[dry-run] 步骤 4:幂等移除 $PATCH_YML 里旧的 better-sidebar 手动挂载行(避免双挂载)"
|
|
204
|
+
if [ "$RESTART" = true ]; then say "[dry-run] 步骤 5:pm2 restart dsh-web"; else say "[dry-run] 步骤 5:提示用户手动重启 DSH"; fi
|
|
205
|
+
exit 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# 步骤 1:预写 workspace 设置(幂等),保证 pnpm 不拦截构建、不放行旧版本策略
|
|
209
|
+
ensure_workspace_settings
|
|
210
|
+
|
|
211
|
+
# 步骤 2:官方 CLI 安装 + bundle 自动注册(含挂载)
|
|
212
|
+
say "执行 $CLI plugin --profile $PROFILE_NAME add $PKG@$SPEC ..."
|
|
213
|
+
if ! $CLI plugin --profile "$PROFILE_NAME" add "$PKG@$SPEC" 2>&1 | tail -n +1; then
|
|
214
|
+
warn "dsh plugin add 失败。已预写 allowBuilds 与 minimumReleaseAgeExclude,仍失败的可能原因:"
|
|
215
|
+
warn " - 网络/登录问题:npm registry 不可达或需要登录。"
|
|
216
|
+
warn " - 依赖安装冲突:可手动重试 cd $PROFILE_DIR && pnpm install。"
|
|
217
|
+
exit 1
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# 校验 bundle 已注册(挂载生效的判据)
|
|
221
|
+
if ! node -e '
|
|
222
|
+
const fs = require("fs");
|
|
223
|
+
const p = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
224
|
+
const bundles = p.dsh?.profile?.bundles ?? [];
|
|
225
|
+
process.exit(bundles.includes(process.argv[2]) ? 0 : 1);
|
|
226
|
+
' "$PROFILE_DIR/package.json" "$PKG"; then
|
|
227
|
+
warn "dsh-coding-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。"
|
|
228
|
+
warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。"
|
|
229
|
+
exit 1
|
|
230
|
+
fi
|
|
231
|
+
say "bundle 已注册:dsh.profile.bundles 包含 ${PKG}(下次启动自动挂载)"
|
|
232
|
+
|
|
233
|
+
# 步骤 3:幂等移除旧的 manual 挂载行(避免与 bundle 双挂载)
|
|
234
|
+
MOUNT_RESULT="$(node -e '
|
|
235
|
+
const fs = require("fs");
|
|
236
|
+
const p = process.argv[1];
|
|
237
|
+
const lines = fs.readFileSync(p, "utf8").split("\n");
|
|
238
|
+
const out = [];
|
|
239
|
+
let i = 0;
|
|
240
|
+
let removed = false;
|
|
241
|
+
while (i < lines.length) {
|
|
242
|
+
const line = lines[i];
|
|
243
|
+
if (/^[ \t]*- insert:\s*$/.test(line)) {
|
|
244
|
+
const block = [line];
|
|
245
|
+
let j = i + 1;
|
|
246
|
+
while (j < lines.length && lines[j].trim() !== "" && !/^-\s/.test(lines[j])) {
|
|
247
|
+
block.push(lines[j]);
|
|
248
|
+
j++;
|
|
249
|
+
}
|
|
250
|
+
if (block.some((l) => /id:\s*better-sidebar\b/.test(l))) {
|
|
251
|
+
while (out.length && /^[ \t]*#/.test(out[out.length - 1])) out.pop();
|
|
252
|
+
i = j;
|
|
253
|
+
removed = true;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
out.push(line);
|
|
258
|
+
i++;
|
|
259
|
+
}
|
|
260
|
+
if (!removed) {
|
|
261
|
+
console.log("none");
|
|
262
|
+
} else {
|
|
263
|
+
const t = out.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
264
|
+
fs.writeFileSync(p, t);
|
|
265
|
+
console.log("removed");
|
|
266
|
+
}
|
|
267
|
+
' "$PATCH_YML")"
|
|
268
|
+
[ "$MOUNT_RESULT" = "removed" ] \
|
|
269
|
+
&& say "已从 $PATCH_YML 移除旧的 better-sidebar 手动挂载行(bundle 通道接管挂载)" \
|
|
270
|
+
|| say "无旧手动挂载行,跳过"
|
|
271
|
+
|
|
272
|
+
say "安装完成:$PKG@$SPEC"
|
|
273
|
+
|
|
274
|
+
# 步骤 4:重启提示
|
|
275
|
+
if [ "$RESTART" = true ]; then
|
|
276
|
+
if command -v pm2 >/dev/null 2>&1; then
|
|
277
|
+
say "重启 dsh-web(pm2)..."
|
|
278
|
+
pm2 restart dsh-web || warn "pm2 restart 失败,请手动重启 DSH"
|
|
279
|
+
else
|
|
280
|
+
warn "未找到 pm2,请手动重启 DSH(如:pm2 restart dsh-web 或 dsh web)"
|
|
281
|
+
fi
|
|
282
|
+
else
|
|
283
|
+
say "下一步:重启 DSH 并硬刷新(Cmd/Ctrl+Shift+R)使新副本生效。"
|
|
284
|
+
if command -v pm2 >/dev/null 2>&1; then
|
|
285
|
+
say "本机可用:pm2 restart dsh-web(会短暂断开当前页面会话)"
|
|
286
|
+
fi
|
|
287
|
+
fi
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model-facing `sidebar_open` tool and its delivery registry.
|
|
3
|
+
*
|
|
4
|
+
* One tool lets the model actively open a local file, a local folder (as a
|
|
5
|
+
* tree rooted there), or an HTTP(S) page in the CALLING session's sidebar.
|
|
6
|
+
* Mirroring the agent-terminal tools, the tool binds to the calling agent's
|
|
7
|
+
* session through `exec.agent.session.id` — the model never passes a
|
|
8
|
+
* sessionId, and opens for non-active sessions are queued until that
|
|
9
|
+
* session's sidebar view is next connected.
|
|
10
|
+
*
|
|
11
|
+
* Delivery is a host→browser push over the dedicated `/sidebar/ws/agent-opens`
|
|
12
|
+
* endpoint (the same pattern as `/sidebar/ws/agent-terminals`): the registry
|
|
13
|
+
* keeps a per-session queue; a push is consumed on send (`delivered: true`
|
|
14
|
+
* means a sidebar view was attached at call time), otherwise the request
|
|
15
|
+
* stays queued and is replayed when a view for that session attaches.
|
|
16
|
+
*
|
|
17
|
+
* Conventions (per plugin-development-guide.md §3):
|
|
18
|
+
* C1 — parameters schema-validated before `execute` runs.
|
|
19
|
+
* C4 — `execute` returns one canonical JSON value; `render` is a separate
|
|
20
|
+
* pure text projection.
|
|
21
|
+
* C6 — `exec.signal.throwIfAborted()` before any fs work.
|
|
22
|
+
* C10 — no UI/transport vocabulary in the canonical value.
|
|
23
|
+
*/
|
|
24
|
+
import { randomUUID } from 'node:crypto'
|
|
25
|
+
import { stat } from 'node:fs/promises'
|
|
26
|
+
import { isAbsolute, join, resolve } from 'node:path'
|
|
27
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
28
|
+
import type { ToolRunContext } from '@deepseek-ai/dsh-tools'
|
|
29
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
|
30
|
+
import type { Context } from './context-types.ts'
|
|
31
|
+
import type { SidebarPrefs } from './prefs-shared.ts'
|
|
32
|
+
|
|
33
|
+
/** What the model asked to open. */
|
|
34
|
+
export type AgentOpenKind = 'file' | 'folder' | 'url'
|
|
35
|
+
|
|
36
|
+
/** One pending/broadcast open request (the wire face over the push socket). */
|
|
37
|
+
export interface AgentOpenRequest {
|
|
38
|
+
/** Opaque id (host-generated; the client ignores it beyond dedupe/debug). */
|
|
39
|
+
id: string
|
|
40
|
+
/** The session whose sidebar the open is targeted at. */
|
|
41
|
+
sessionId: string
|
|
42
|
+
kind: AgentOpenKind
|
|
43
|
+
/** Absolute local path (file/folder) or http(s) URL. */
|
|
44
|
+
target: string
|
|
45
|
+
/** Tab title the client should use (basename / hostname / caller-supplied). */
|
|
46
|
+
title: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** One subscribed sidebar view's sender. */
|
|
50
|
+
type Sender = (request: AgentOpenRequest) => void
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Per-session queue of open requests plus the connected sidebar views.
|
|
54
|
+
*
|
|
55
|
+
* Lifecycle: `enqueue` adds a request and — when at least one view for the
|
|
56
|
+
* session is attached — pushes it immediately and removes it from the queue
|
|
57
|
+
* (consume-on-send: a reconnect must never replay an open the client already
|
|
58
|
+
* applied, and the browser tab type has no per-URL dedupe, so replaying
|
|
59
|
+
* would mint duplicate tabs). With no attached view the request stays queued
|
|
60
|
+
* and `attach` replays it on connect. `drainAll` drops every queued request
|
|
61
|
+
* (the feature was turned off); `dispose` also drops every subscriber.
|
|
62
|
+
*/
|
|
63
|
+
export class AgentOpenRegistry {
|
|
64
|
+
private pending = new Map<string, AgentOpenRequest[]>()
|
|
65
|
+
private subscribers = new Map<string, Set<Sender>>()
|
|
66
|
+
|
|
67
|
+
/** Queue one open and deliver it immediately when a view is attached.
|
|
68
|
+
* @returns the request id and whether a connected view received it now. */
|
|
69
|
+
enqueue(sessionId: string, kind: AgentOpenKind, target: string, title: string): { id: string; delivered: boolean } {
|
|
70
|
+
const request: AgentOpenRequest = { id: randomUUID(), sessionId, kind, target, title }
|
|
71
|
+
const list = this.pending.get(sessionId) ?? []
|
|
72
|
+
list.push(request)
|
|
73
|
+
this.pending.set(sessionId, list)
|
|
74
|
+
const views = this.subscribers.get(sessionId)
|
|
75
|
+
if (views !== undefined && views.size > 0) {
|
|
76
|
+
for (const send of views) send(request)
|
|
77
|
+
this.pending.delete(sessionId)
|
|
78
|
+
return { id: request.id, delivered: true }
|
|
79
|
+
}
|
|
80
|
+
return { id: request.id, delivered: false }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Attach one sidebar view (replays queued requests; consume-on-send).
|
|
84
|
+
* @returns the disposer detaching the view. */
|
|
85
|
+
attach(sessionId: string, send: Sender): () => void {
|
|
86
|
+
let views = this.subscribers.get(sessionId)
|
|
87
|
+
if (views === undefined) {
|
|
88
|
+
views = new Set()
|
|
89
|
+
this.subscribers.set(sessionId, views)
|
|
90
|
+
}
|
|
91
|
+
views.add(send)
|
|
92
|
+
const queued = this.pending.get(sessionId) ?? []
|
|
93
|
+
if (queued.length > 0) {
|
|
94
|
+
for (const request of queued) send(request)
|
|
95
|
+
this.pending.delete(sessionId)
|
|
96
|
+
}
|
|
97
|
+
return () => {
|
|
98
|
+
const current = this.subscribers.get(sessionId)
|
|
99
|
+
current?.delete(send)
|
|
100
|
+
if (current !== undefined && current.size === 0) this.subscribers.delete(sessionId)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Drop every queued request (the feature was turned off mid-session). */
|
|
105
|
+
drainAll(): void {
|
|
106
|
+
this.pending.clear()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Drop the queue and every subscriber (plugin teardown). */
|
|
110
|
+
dispose(): void {
|
|
111
|
+
this.pending.clear()
|
|
112
|
+
this.subscribers.clear()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Extract the calling agent or throw the canonical "no agent" error. */
|
|
117
|
+
function requireAgent(agent: ToolRunContext['agent']): NonNullable<ToolRunContext['agent']> {
|
|
118
|
+
if (agent === undefined) {
|
|
119
|
+
throw new Error('sidebar_open requires an initiating agent')
|
|
120
|
+
}
|
|
121
|
+
return agent
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Resolve the calling agent's session id (the queue scope + ownership key). */
|
|
125
|
+
function sessionIdOf(exec: ToolRunContext): string {
|
|
126
|
+
return requireAgent(exec.agent).session.id
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Pure text projection helper (the canonical value is already structured). */
|
|
130
|
+
function textRender<T>(fn: (value: T) => string): (_args: unknown, value: unknown) => ContentBlock[] {
|
|
131
|
+
return (_args, value) => [{ type: 'text', text: fn(value as T) }]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Classify a raw target: http(s) URL or a local path (stat-driven). */
|
|
135
|
+
async function classifyTarget(raw: string, cwd: string): Promise<{ kind: AgentOpenKind; target: string; title: string }> {
|
|
136
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
137
|
+
let parsed: URL
|
|
138
|
+
try {
|
|
139
|
+
parsed = new URL(raw)
|
|
140
|
+
} catch {
|
|
141
|
+
throw new Error(`"${raw}" is not a valid URL`)
|
|
142
|
+
}
|
|
143
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
144
|
+
throw new Error('sidebar_open only accepts http:// and https:// URLs')
|
|
145
|
+
}
|
|
146
|
+
const title = parsed.hostname !== '' ? parsed.hostname : raw
|
|
147
|
+
return { kind: 'url', target: raw, title }
|
|
148
|
+
}
|
|
149
|
+
// Any other scheme (`file:`, `javascript:`, `vscode:`, ...) is refused —
|
|
150
|
+
// the model must use the plain URL form or a local path. A Windows drive
|
|
151
|
+
// prefix (`C:\` / `C:/`) is a PATH, not a scheme, and must not be caught.
|
|
152
|
+
if (!isWindowsDrivePrefix(raw) && /^[a-z][a-z0-9+.-]*:/i.test(raw)) {
|
|
153
|
+
throw new Error('sidebar_open only accepts http:// and https:// URLs; use a local path for files')
|
|
154
|
+
}
|
|
155
|
+
const target = resolve(isAbsolute(raw) ? raw : join(cwd, raw))
|
|
156
|
+
let info
|
|
157
|
+
try {
|
|
158
|
+
info = await stat(target)
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const code = (error as NodeJS.ErrnoException).code
|
|
161
|
+
if (code === 'ENOENT') throw new Error(`"${raw}" does not exist (resolved to "${target}")`)
|
|
162
|
+
if (code === 'EACCES' || code === 'EPERM') throw new Error(`"${target}" is not readable`)
|
|
163
|
+
throw new Error(`cannot open "${target}": ${error instanceof Error ? error.message : String(error)}`)
|
|
164
|
+
}
|
|
165
|
+
const title = basenameOf(target)
|
|
166
|
+
return { kind: info.isDirectory() ? 'folder' : 'file', target, title: title === '' ? raw : title }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The last path segment (mirror of the client's FileTree baseName). */
|
|
170
|
+
function basenameOf(path: string): string {
|
|
171
|
+
const trimmed = path.replace(/[\\/]+$/, '')
|
|
172
|
+
const at = Math.max(trimmed.lastIndexOf('/'), trimmed.lastIndexOf('\\'))
|
|
173
|
+
return at === -1 ? trimmed : trimmed.slice(at + 1)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Whether a raw target starts with a Windows drive prefix (`C:\` / `C:/`). */
|
|
177
|
+
function isWindowsDrivePrefix(raw: string): boolean {
|
|
178
|
+
return /^[a-zA-Z]:[\\/]/.test(raw)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Register the `sidebar_open` tool against the host tool registry. The tool
|
|
183
|
+
* is gated by the side-card setting `agentOpenTools` (the caller registers
|
|
184
|
+
* and unregisters it); `readPrefs` supplies the live prefs so a disabled
|
|
185
|
+
* target tab type (editor/browser) is reported to the model instead of
|
|
186
|
+
* silently no-oping on the client. `resolveCwd` threads the calling
|
|
187
|
+
* session's live cwd so relative paths resolve the same way the sidebar's
|
|
188
|
+
* own routes do.
|
|
189
|
+
* @param ctx - host plugin context (carries the tools service).
|
|
190
|
+
* @param registry - the open-request registry (per-session queue + views).
|
|
191
|
+
* @param resolveCwd - async cwd resolver for one session id. Resolves through
|
|
192
|
+
* the session header, the client-supplied cwd, and the persistence index
|
|
193
|
+
* before falling back to the host process cwd (production always provides
|
|
194
|
+
* persistence, so the fallback is reached only in tests / stripped-down hosts).
|
|
195
|
+
* @param readPrefs - live resolved side card prefs (for tab enable gates).
|
|
196
|
+
* @returns a disposer that unregisters the tool.
|
|
197
|
+
*/
|
|
198
|
+
export function registerOpenTool(
|
|
199
|
+
ctx: Context,
|
|
200
|
+
registry: AgentOpenRegistry,
|
|
201
|
+
resolveCwd: (sessionId: string) => Promise<string>,
|
|
202
|
+
readPrefs: () => SidebarPrefs,
|
|
203
|
+
): () => void {
|
|
204
|
+
return ctx.tools.register(defineTool({
|
|
205
|
+
name: 'sidebar_open',
|
|
206
|
+
description:
|
|
207
|
+
'Open a local file, a local folder, or an HTTP(S) page in the sidebar of the calling conversation. '
|
|
208
|
+
+ 'A file opens in the sidebar editor (per-path dedupe: an already-open file is focused); a folder opens a file window whose tree is rooted at that folder; '
|
|
209
|
+
+ 'a URL opens in the sidebar browser (sandboxed iframe). '
|
|
210
|
+
+ 'The panel auto-expands for content opens and the tab title defaults to the file/folder name or the URL hostname. '
|
|
211
|
+
+ 'The path may be absolute or relative to the session working directory. '
|
|
212
|
+
+ 'The open lands in the CALLING session\'s sidebar: while that session\'s sidebar view is not connected '
|
|
213
|
+
+ '(e.g. the session is not the active one), the open is queued and delivered when the session sidebar is next shown — '
|
|
214
|
+
+ 'the result reports `delivered` so you know whether it is visible right now. '
|
|
215
|
+
+ 'The side card setting "model opens files/folders/pages in the sidebar" must be on, and the target tab type must be enabled in that session\'s settings.',
|
|
216
|
+
parameters: {
|
|
217
|
+
target: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
required: true,
|
|
220
|
+
description: 'Absolute or session-cwd-relative local path, or an http:// / https:// URL.',
|
|
221
|
+
},
|
|
222
|
+
title: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'Optional tab title (defaults to the file/folder name or the URL hostname).',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
output: {
|
|
228
|
+
schema: {
|
|
229
|
+
type: 'object',
|
|
230
|
+
additionalProperties: false,
|
|
231
|
+
properties: {
|
|
232
|
+
kind: { type: 'string', required: true, description: 'What was opened: file | folder | url.' },
|
|
233
|
+
target: { type: 'string', required: true, description: 'The absolute path or URL the open was requested for.' },
|
|
234
|
+
title: { type: 'string', required: true, description: 'The tab title used (provided title, basename, or hostname).' },
|
|
235
|
+
delivered: {
|
|
236
|
+
type: 'boolean',
|
|
237
|
+
required: true,
|
|
238
|
+
description: 'Whether the open was pushed to a connected sidebar at call time (false = queued until the session sidebar is next shown).',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
render: textRender((v: { kind: AgentOpenKind; target: string; title: string; delivered: boolean }) =>
|
|
243
|
+
v.delivered
|
|
244
|
+
? `Opened ${v.kind} "${v.title}" (${v.target}) in the sidebar.`
|
|
245
|
+
: `Requested opening ${v.kind} "${v.title}" (${v.target}) in the sidebar — the session sidebar is not connected yet, so the open is queued and will appear when it is next shown.`,
|
|
246
|
+
),
|
|
247
|
+
},
|
|
248
|
+
execute: async (args: { target: string; title?: string }, exec: ToolRunContext) => {
|
|
249
|
+
exec.signal.throwIfAborted()
|
|
250
|
+
const sessionId = sessionIdOf(exec)
|
|
251
|
+
const cwd = await resolveCwd(sessionId)
|
|
252
|
+
const { kind, target, title: defaultTitle } = await classifyTarget(args.target, cwd)
|
|
253
|
+
// A disabled target tab type would make the client no-op the open:
|
|
254
|
+
// report the real cause to the model instead of a silent success.
|
|
255
|
+
const prefs = readPrefs()
|
|
256
|
+
const tab = kind === 'url' ? 'browser' : 'editor'
|
|
257
|
+
if (prefs.tabsEnabled[tab] === false) {
|
|
258
|
+
throw new Error(`the built-in ${tab} tab is disabled in the side card settings; ask the user to enable it (or disable this tool)`)
|
|
259
|
+
}
|
|
260
|
+
const title = args.title !== undefined && args.title.trim() !== '' ? args.title : defaultTitle
|
|
261
|
+
const { delivered } = registry.enqueue(sessionId, kind, target, title)
|
|
262
|
+
return { kind, target, title, delivered }
|
|
263
|
+
},
|
|
264
|
+
}))
|
|
265
|
+
}
|