dsh-composer-plus 1.22.0 → 1.23.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/README.zh.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  <p align="center"><img src="docs/images/hero.webp" width="820" alt="装有 Composer Plus 的 DeepSeek Harness:正在运行的项目在侧边栏中显示加载图标,改动面板显示 git diff"></p>
16
16
 
17
- 这是非官方的社区插件。插件只在检查更新时访问网络(GitHub releases 和 npm registry);关闭 **自动更新** 后,启动时就不再发出这些请求。只有在你点击 **推荐功能** 时,**功能创意** 才会调用对话自己的模型。**下一条消息预测**(默认开启)每完成一个回合都会调用一次在设置中选择的模型(除非你另选,否则为 dsh 的默认模型),不需要点击;在 **设置 → Composer Plus** 中关闭它,即可停止这些调用。**输入时补全**(默认关闭)在你开启后,会在你每次输入停顿时调用为它选择的模型(默认使用预测的模型)。只有在你点击魔法棒时,**优化提示词** 才会调用模型。**任务看板** 会为你在看板上开始的每个任务调用一次新对话的模型,用来生成标签。使用 **AI 摘要** 设置时(默认是 **快速(不用 AI)**),当你在看板或 Tether 中查看正在运行的任务的进度时,它还会调用该对话的模型,写一段简短的进度说明。**目标检查**(默认开启)在智能体每次于自动轮次中把 `/goal` 标记为完成时调用一次模型。**手机连接**(默认关闭)只有在你开启后,才会接受来自本地网络或 tailnet 的 iPhone 连接。
17
+ 这是非官方的社区插件。检查更新会访问网络(GitHub releases 和 npm registry);关闭 **自动更新** 后,启动时就不再发出这些请求。 **土耳其语技能包**(默认开启)仅在 **应用语言** 为 Türkçe 时从 GitHub 下载 turkish-anti-ai-skill,并每天检查一次新版本;在 **设置 → Composer Plus** 中关闭它即可停止这些请求。只有在你点击 **推荐功能** 时,**功能创意** 才会调用对话自己的模型。**下一条消息预测**(默认开启)每完成一个回合都会调用一次在设置中选择的模型(除非你另选,否则为 dsh 的默认模型),不需要点击;在 **设置 → Composer Plus** 中关闭它,即可停止这些调用。**输入时补全**(默认关闭)在你开启后,会在你每次输入停顿时调用为它选择的模型(默认使用预测的模型)。只有在你点击魔法棒时,**优化提示词** 才会调用模型。**任务看板** 会为你在看板上开始的每个任务调用一次新对话的模型,用来生成标签。使用 **AI 摘要** 设置时(默认是 **快速(不用 AI)**),当你在看板或 Tether 中查看正在运行的任务的进度时,它还会调用该对话的模型,写一段简短的进度说明。**目标检查**(默认开启)在智能体每次于自动轮次中把 `/goal` 标记为完成时调用一次模型。**手机连接**(默认关闭)只有在你开启后,才会接受来自本地网络或 tailnet 的 iPhone 连接。
18
18
 
19
19
  ## 安装
20
20
 
@@ -29,7 +29,7 @@ dsh plugin --profile web add dsh-composer-plus
29
29
 
30
30
  然后重启 dsh。(CLI 无法安装到桌面版 profile 中;桌面版 profile 由应用自己管理。)
31
31
 
32
- > dsh 使用的 pnpm 会跳过发布不到 24 小时的版本。刚发布新版本时,请加上版本号:`dsh-composer-plus@1.22.0`。
32
+ > dsh 使用的 pnpm 会跳过发布不到 24 小时的版本。刚发布新版本时,请加上版本号:`dsh-composer-plus@1.23.0`。
33
33
 
34
34
  ## 功能
35
35
 
@@ -48,6 +48,7 @@ dsh plugin --profile web add dsh-composer-plus
48
48
  | **保持输入框焦点** | <a id="feature-keepfocus"></a>发送 `/` 命令或技能后,光标仍留在输入框中,你可以立即继续输入。对于输入命令后按 `Enter` 的键盘宏,这同样有效。dsh 在解析命令时会把输入框设为只读,浏览器随后会移除焦点。本功能会把焦点放回输入框,除非菜单、对话框或其他输入框正持有焦点。 | <a href="assets/features/keepFocus.webp"><img src="assets/features/keepFocus.webp" width="160" alt="截图:保持输入框焦点"></a> |
49
49
  | **问题卡片适应窗口** | <a id="feature-questionfit"></a>代理向你提问时,问题和答案在卡片中一起滚动,**跳过**和**提交**始终显示在底部。卡片使用窗口的高度(减去 200 px,留给对话标题和一部分对话),所以会随窗口变大变小。没有本功能时,dsh 把卡片限制在窗口的 60%(最多 520 px),并且只有答案可以滚动:较长的问题会把它自己的结尾、答案和按钮挤出卡片。 | <a href="assets/features/questionFit.webp"><img src="assets/features/questionFit.webp" width="160" alt="截图:问题卡片适应窗口"></a> |
50
50
  | **对话标题栏适应窗口** | <a id="feature-headerfit"></a>窗口较窄时,dsh 对话标题栏中的按钮(模式、子代理、后台任务、打开方式、分支标签和授权标签)不再互相遮挡。对话标题至少保留 120 px;放不下的按钮换到右侧的第二行,每个按钮仍可点击。所有内容能放在一行时(宽窗口),标题栏与 dsh 原样一致。没有本功能时,dsh 只压缩标题:有子代理和后台任务时,在较窄的对话栏中按钮会互相覆盖。即使关闭本开关,分支标签(**每个对话一个分支**)和授权标签(**本对话内允许**)也会让标题栏适应窗口。 | <a href="assets/features/headerFit.webp"><img src="assets/features/headerFit.webp" width="160" alt="截图:对话标题栏适应窗口"></a> |
51
+ | **记住窗口大小** | <a id="feature-windowsize"></a>dsh 桌面应用每次启动时都以 1280 × 820 打开窗口。开启本功能后,窗口会以上次使用时的大小和位置打开。每次调整窗口大小或移动窗口时都会保存,下次启动、插件加载后恢复。全屏或最小化的窗口不会被保存;如果保存的位置所在的显示器现在未连接,窗口会回到主显示器。仅限桌面应用:浏览器标签页无法改变自己窗口的大小。 | <a href="assets/features/windowSize.webp"><img src="assets/features/windowSize.webp" width="160" alt="截图:记住窗口大小"></a> |
51
52
  | **一键继续** | <a id="feature-continuebutton"></a>回合被停止、失败或中断后,空输入框会显示 **▶**。点击一次即可在现有对话的基础上继续,不需要在输入框中输入内容,也不会添加可见的对话消息(模型会收到一条简短的隐藏提示“从停止的地方继续”,因为部分提供商,例如 Claude,会拒绝以助手回复结尾的请求)。队列中的消息会保留:▶ 只继续被停止的任务,之后再运行队列。已完成的回合仍显示普通的发送按钮。 | <a href="assets/features/continueButton.webp"><img src="assets/features/continueButton.webp" width="160" alt="截图:一键继续"></a> |
52
53
  | **重启后继续** | <a id="feature-resumeafterrestart"></a>默认关闭:请在 **设置 → Composer Plus** 中开启。如果你在对话仍在运行时退出 dsh(或 dsh 被关闭),再次打开 dsh 后,该对话会自动继续,并使用与“一键继续”相同的隐藏提示。如果当时有目标轮次(`/goal`)正在运行,该目标会继续下一轮(dsh 每次启动时都会暂停目标,直到你恢复它们)。你停止的、已完成的或失败的对话不会继续。dsh 本身没有这项设置:它只会把这样的回合标记为已中断。 | <a href="assets/features/resumeAfterRestart.webp"><img src="assets/features/resumeAfterRestart.webp" width="160" alt="截图:重启后继续"></a> |
53
54
  | **队列拖拽排序** | <a id="feature-queuereorder"></a>排队的消息会带一个把手(⠿)。拖动它即可调整顺序,或者让把手获得焦点后按 `↑` / `↓`。按 `Esc` 取消。 | <a href="assets/features/queueReorder.webp"><img src="assets/features/queueReorder.webp" width="160" alt="截图:队列拖拽排序"></a> |
@@ -69,6 +70,8 @@ dsh plugin --profile web add dsh-composer-plus
69
70
  | **在新对话中继续** | <a id="feature-handoffchat"></a>在侧边栏中右键点击对话(或打开它的 **…** 菜单)→ **在新对话中继续…**。调用一次该对话自己的模型,根据对话显示的内容写出交接说明:任务、产出、已完成、决定、未解决问题和下一步(工具调用及其结果是证据;你回滚的步骤不计入)。说明会在一个对话框中打开:阅读并修改它。**重新生成** 会写一份新说明。**开始新对话**(或 `Cmd/Ctrl+Enter`)会在同一项目中打开一个新对话,以该说明和旧对话的链接作为第一条消息;旧对话保持不变。当长对话已满或变慢,或者想第二天继续时,可以使用它。不会向项目写入任何内容。 | <a href="assets/features/handoffChat.webp"><img src="assets/features/handoffChat.webp" width="160" alt="截图:在新对话中继续"></a> |
70
71
  | **上下文明细** | <a id="feature-contextbreakdown"></a>右侧边栏 → `+` → **上下文**。该页面显示当前是什么在占用打开的对话的模型上下文,以及它占用了上下文窗口的多少:系统提示词、项目指令(AGENTS.md)、技能、你的消息、智能体回复和工具调用、工具结果(按工具)、MCP 结果(按服务器)、工具定义、压缩摘要和其他上下文说明,每项附令牌数和占比。点击某一部分可查看它的工具或服务器。它只读取对话日志(`/compact` 和回滚之后的状态),不调用模型;每个回合结束后以及点击 **刷新** 时会重新读取。可用它查看对话为何变慢或已满,例如一个很大的 MCP 工具结果或大量工具定义。 | <a href="assets/features/contextBreakdown.webp"><img src="assets/features/contextBreakdown.webp" width="160" alt="截图:上下文明细"></a> |
71
72
  | **计划步骤** | <a id="feature-plannedsteps"></a>默认关闭:请在 **设置 → Composer Plus** 中开启。智能体会获得工具 `plan_next_steps`:它可以在发出某条命令的同一条消息中,预先计划接下来的命令及其确切参数和条件,例如“构建通过后运行 `npm test`”。结果满足条件时,Composer Plus 会自行运行下一步,无需再次调用模型,因此长对话消耗的令牌更少。该步骤在对话中显示为一行简短说明“Composer Plus · Planned steps: ran `test` … without a model call”,随后是它的工具调用和结果。仅限 `bash`、`read`、`grep` 和 `glob` 步骤;审批、沙箱和受保护的文件照常作用于每一步。条件不满足、同时有两个步骤就绪、最后一步完成后以及你发送消息时,模型会重新作答。这个想法来自 HarnessMM(arXiv 2610.32813)。 | <a href="assets/features/plannedSteps.webp"><img src="assets/features/plannedSteps.webp" width="160" alt="截图:计划步骤"></a> |
73
+ | **土耳其语技能包** | <a id="feature-turkishskill"></a>仅在 **应用语言** 为 Türkçe 时生效(默认开启;可在 **设置 → Composer Plus** 中关闭)。Composer Plus 从 GitHub 下载开源的 [turkish-anti-ai-skill](https://github.com/yucezerey/turkish-anti-ai-skill)(MIT,作者 Yüce Zerey),并把它加入 dsh 的技能。请智能体润色、改写或撰写土耳其语文本时,dsh 会加载该技能,其中的规则帮助智能体写出自然、没有 AI 痕迹的土耳其语。只保留技能包的 Markdown 文件;每个文件都必须与 GitHub 的文件列表一致,技能包中的脚本不会运行。插件每天向 GitHub 查询一次新版本并安装。该行显示已安装的版本,或未安装的原因。在其他语言下不会安装该技能;把语言切回 Türkçe 后它会回来。 | <a href="assets/features/turkishSkill.webp"><img src="assets/features/turkishSkill.webp" width="160" alt="截图:土耳其语技能包"></a> |
74
+ | **理清代码库** | <a id="feature-untanglecodebase"></a>默认开启;可在 **设置 → Composer Plus** 中关闭。把 [caldoai/skills](https://github.com/caldoai/skills) 中的技能 `/untangle-codebase`(MIT,© 2026 Maze)加入 dsh 的技能列表。在对话中输入 `/untangle-codebase`(它会出现在 `/` 菜单中),可在后面加上要关注的部分,例如 `/untangle-codebase src/api`,然后发送。智能体会先问你是否还要删除只是复述代码的注释,以及发现不了真实缺陷的测试。然后它列出所有重复代码、隐藏的耦合、不必要的耦合、只做转发的包装层、无用代码和一次性的特殊处理,从基本原理出发规划修复,以目标的方式完成修复,并用子智能体反复检查,直到没有值得修复的问题为止。只有你能启动它:模型的技能列表中没有它,因此不会在其他对话中消耗令牌。项目中或 `~/.dsh/skills` 中的同名技能优先。请在 **每个对话一个分支** 的对话中启动它,并在 **改动面板** 中查看结果。在 Tether 中:在输入框中输入 `/untangle-codebase`。 | <a href="assets/features/untangleCodebase.webp"><img src="assets/features/untangleCodebase.webp" width="160" alt="截图:理清代码库"></a> |
72
75
  | **受保护的文件** | <a id="feature-protectedfiles"></a>默认开启。智能体自己的文件工具不能读取或更改与你的模式匹配的文件:对于 **读取和写入** 模式,对文件使用的 `read`、`read_image` 和 `grep` 会被拒绝;两种模式下 `write`、`edit` 和 `str_replace_editor` 都会被拒绝。智能体会看到原因,并被告知要问你。默认模式是 `.env*`、`*.pem` 和 `*.key`。设置 → Composer Plus → **受保护的文件**:添加一个模式(例如 `published/**` 或 `config/*.yml`),点击它的类型可在 **读取和写入** 与 **仅写入** 之间切换,用 ✕ 移除它,或点击 **使用默认值**。不含 `/` 的模式会匹配任意文件夹中的文件名。这是护栏,不是沙箱:shell 命令(`cat .env`)、脚本或 MCP 工具仍能访问该文件(见“限制”)。 | <a href="assets/features/protectedFiles.webp"><img src="assets/features/protectedFiles.webp" width="160" alt="截图:受保护的文件"></a> |
73
76
  | **禁止的命令** | <a id="feature-blockedcommands"></a>默认开启,列表为空。智能体的 shell 命令(`bash`、`pwsh`)不能运行与你的规则匹配的命令:**阻止** 规则会拒绝调用(智能体会看到原因,并被告知要问你),**先询问** 规则会显示 dsh 的审批卡片,只有你允许后命令才会运行。设置 → Composer Plus → **禁止的命令**:输入一条规则(例如 `npm publish`、`git push --force` 或 `ssh *192.168.2.19`),点击类型可在 **阻止** 与 **先询问** 之间切换,然后点击 **添加**;点击规则的类型可以更改它,用 ✕ 移除它。一条规则是一个命令名和必须按此顺序出现在它后面的词;`*` 匹配任意文本。它也能找到 `&&`、`;`、`\|` 之后,`$(…)`、`bash -c "…"` 或 `eval` 之中,以及 `sudo` 或 `env` 之后的命令。这是护栏,不是沙箱:变量(`$CMD`)、脚本文件或其他程序仍能运行该命令(见“限制”)。 | <a href="assets/features/blockedCommands.webp"><img src="assets/features/blockedCommands.webp" width="160" alt="截图:禁止的命令"></a> |
74
77
  | **任务看板** | <a id="feature-taskboard"></a>左侧边栏 → **任务看板**,或在 dsh 的任意位置按 `Cmd+Shift+K`(Windows 和 Linux 上为 `Ctrl+Shift+K`)。dsh 对话的看板,显示与侧边栏相同的实时状态:等待你审批、回答或审阅计划的对话在**审核**中并带橙色圆点,运行中的对话在**进行中**,尚未打开的已完成对话在**已完成**中(绿色)。打开对话后,它的卡片会自动离开**已完成**;点击绿色卡片下方的**最近完成 (N)**,可查看你已读的已完成对话(最新 10 个,然后是**显示更早的 N 个**;展开或收起状态保存在此设备上)。在看板后面打开着时完成的对话(**新建任务**)会保持绿色,直到你打开它。点击看板顶部的项目名称打开项目列表,然后选择一个项目或**所有项目**;“每个对话一个分支”的文件夹算作它所属的项目。选择后、按 `Esc` 或点击列表外部时,列表会关闭。在列表顶部的**搜索项目**中输入,可按名称查找项目(不区分大小写和重音符号;按 `Esc` 清空搜索框),按 `Enter` 选择第一个匹配项;点击**最近 2 小时内活跃**,则只显示有对话正在等待、正在运行、已完成但尚未查看,或在最近 2 小时内有变化的项目(此设置保存在此设备上)。**新建任务**(或按 `n`)会在看板显示的项目中打开 dsh 自己的新对话(选择**所有项目**时,在 dsh 的新会话所选的项目中打开),因此你可以像在任何对话中一样添加文件、使用命令和选择模型。你的第一条消息发出后,会自动回到看板。在此之前,该对话以**新建任务**显示在**进行中**的最上方:点击它即可写第一条消息。有对话在等待你的项目,在项目列表中带橙色圆点(有任何对话在等待时,顶部的项目名称旁也有橙色圆点)。dsh 为对话命名,对话的模型根据这条消息最多写出 3 个标签。点击卡片可打开它的对话。点击橙色卡片(或其上的**回答**),即可直接在看板上回答;面板中的**打开对话**可打开完整对话:**拒绝**或**允许一次**;计划则选**同意执行**或**要求修改**;问题则选择选项或输入你自己的文字。点击已完成卡片上的 **×**,可把这张卡片从看板移除;展开**最近完成**后点击**已完成**顶部的**全部清除**,可把这些卡片从看板移除:它会先显示将移除哪个项目的多少张卡片,未读(绿色)卡片会保留。之后 8 秒内,**撤销**可把卡片放回;**已完成**下方的**已移除 (N)**列出已从看板移除的卡片,**放回**可让其中一张重新显示在**已完成**中。不会删除任何对话:对话仍保留在 dsh 的侧边栏中,重启后看板仍会记住这些操作(Tether 显示同一个看板),再次运行的对话会回到看板上。将指针指向**进行中**里的卡片(或把键盘焦点移到卡片上),即可看到它的进度:运行了多久、待办列表中的当前步骤、正在运行的工具,以及智能体的最后一行。在设置 → Composer Plus → 任务看板中,**悬停显示进度**可选择**快速(不用 AI)**(默认:只读取对话)或 **AI 摘要**(再向对话的模型发送一次简短调用,额外写出一两句话)。 | <a href="assets/features/taskBoard.webp"><img src="assets/features/taskBoard.webp" width="160" alt="截图:任务看板"></a> |
@@ -84,6 +87,7 @@ dsh plugin --profile web add dsh-composer-plus
84
87
  | **拒绝并附言** | <a id="feature-rejectnote"></a>审批卡片的 **拒绝** 旁边会多出 **拒绝并附言…**。点击它,写下原因或应改做什么,然后点 **拒绝**(或按 `Enter`;`Shift+Enter` 换行,`Esc` 关闭输入框)。附言会先作为一条消息发进正在等待的这一轮,然后请求被拒绝:模型在同一轮中读到拒绝和你的附言,你无需再发一条消息。如果附言发不出去,就什么也不会被拒绝,输入框保持打开。Tether 中:审批页 → Reject with note。 | <a href="assets/features/rejectNote.webp"><img src="assets/features/rejectNote.webp" width="160" alt="截图:拒绝并附言"></a> |
85
88
  | **密钥输入** | <a id="feature-secretinput"></a>代理会获得工具 `request_secret`。当它需要密码、令牌或 API 密钥时,会用这个工具索要,而不是在对话里问你:工具行上会出现一个卡片,显示名称、它为什么需要这个值、一个密码输入框,以及 **保存** 和 **拒绝**。**保存** 会把值写入这台 Mac 上只有你能读取的文件,代理只拿到文件路径,并把它作为命令的输入使用(例如 `"$(cat <path>)"`)。之后的工具结果如果显示该值,会在模型看到之前替换为 `[secret]`。这一轮结束时文件会被删除。**拒绝** 会告诉代理你没有提供该值。在 **设置 → Composer Plus** 中关闭它即可移除该工具。Tether 中:输入框上方的 “Secret: …” 条 → Enter。 | <a href="assets/features/secretInput.webp"><img src="assets/features/secretInput.webp" width="160" alt="截图:密钥输入"></a> |
86
89
  | **引用回复** | <a id="feature-quotereply"></a>在回答中选中一段文字:选区上方会出现 **引用** 按钮。点击它,这段文字会以 Markdown 引用(`> …`)的形式插入输入框的光标处,后面留一个空行,并且输入框会获得焦点,你可以直接在下面写回复。只适用于回答正文,不适用于思考过程、工具行或你自己的消息。引用最多 2,000 个字符。`Cmd+Z`(`Ctrl+Z`)可以撤销。Tether 中:长按一条回答 → Quote(整条回答)。 | <a href="assets/features/quoteReply.webp"><img src="assets/features/quoteReply.webp" width="160" alt="截图:引用回复"></a> |
90
+ | **从文件预览中引用** | <a id="feature-previewquote"></a>在右侧边栏打开一个文件(**工作区文件**,或此对话打开的文件),选中其中几行:选区上方会出现 **引用** 按钮。点击它,文件路径、行号和这些行会插入输入框的光标处,并且输入框会获得焦点,这样智能体就知道你指的是哪一部分。代码和文本文件给出 `src/login.js:2-3`,这些行放在代码块中;Markdown 和 PDF 文件给出路径(PDF 还有页码,`docs/spec.pdf#page=3`)和 `> …` 引用。对话文件夹内的路径是相对路径。最多 2,000 个字符。`Cmd+Z`(`Ctrl+Z`)可以撤销。Tether 中没有。 | <a href="assets/features/previewQuote.webp"><img src="assets/features/previewQuote.webp" width="160" alt="截图:从文件预览中引用"></a> |
87
91
  | **旁支对话** | <a id="feature-sidechat"></a>在输入框中输入旁支问题,然后点击对话气泡按钮(**在旁支对话中提问**):会打开此对话的一个副本,以 dsh 的只读模式回答,而原对话继续工作。副本了解对话到目前为止的内容(仍在队列中等待的消息除外),并沿用其模型。副本输入框上方的横条中有 **带回**:会打开原对话,并把你的问题(`> ` 引用)和回答放入它的输入框。**返回对话** 只打开原对话。旁支对话以“↳ <问题>”留在侧边栏中,可以在那里归档。Tether 中:对话标题菜单 → Side chat…。 | <a href="assets/features/sideChat.webp"><img src="assets/features/sideChat.webp" width="160" alt="截图:旁支对话"></a> |
88
92
  | **差异批注** | <a id="feature-diffcomments"></a>在 **改动面板** 中打开一个文件的差异,点击某一行的 **+**(按住 Shift 点击同一区块的另一行可选范围),写下批注并点击 **保存批注**。此对话的批注会在面板顶部的横条中等待;**发送到输入框** 会把它们以 Markdown 放到输入框的光标处:`path:12-14`、作为 `diff` 代码块的这些行和你的批注。**清除** 会丢弃它们。批注保存在此浏览器中,直到你发送或清除。需要 **改动面板**。Tether 中:文件差异 → 长按一行 → Add note…。 | <a href="assets/features/diffComments.webp"><img src="assets/features/diffComments.webp" width="160" alt="截图:差异批注"></a> |
89
93
  | **项目状态** | <a id="feature-projectstatus"></a>侧边栏中的项目行会显示与其对话相同的加载图标 / 等待 / 完成标记。因此,即使项目已折叠,你也能知道其中有对话正在运行或需要你处理。等待你处理的对话优先显示,其次是你尚未打开的已完成对话(绿色圆点,即使另一个对话仍在运行也会显示),最后是加载图标。将鼠标悬停在上面可查看详情。在**按工作区树**中,父文件夹的行也会显示其中每个项目(例如 `.worktrees/` 中的每个分支文件夹)的对话状态,折叠时也一样。 | <a href="assets/features/projectStatus.webp"><img src="assets/features/projectStatus.webp" width="160" alt="截图:项目状态"></a> |
@@ -95,12 +99,14 @@ dsh plugin --profile web add dsh-composer-plus
95
99
  | **按状态排序** | <a id="feature-statusorder"></a>侧边栏 → 视图选项 → **排序方式** → **按状态**(配合 **单列表** 使用)。已完成的对话排在最前(即带 dsh “完成”圆点的对话,以出错结束的回合也会显示这个圆点),然后是正在运行或等待中的对话,再然后是带有活跃自动化任务(Schedule)的对话,其余对话按 dsh 的顺序排列。打开已完成的对话后,它会往下移。只有视图会改变:选择 **手动排序** 或 **最近更新** 即可恢复。 | <a href="assets/features/statusOrder.webp"><img src="assets/features/statusOrder.webp" width="160" alt="截图:按状态排序"></a> |
96
100
  | **后台通知** | <a id="feature-notifications"></a>dsh 在后台时,如果回合完成(通知显示回复的第一行),或对话确实在等你处理,你都会收到通知。等你处理的情况包括:审批(显示工具名称)、回答(显示问题)或计划审阅。请求必须在 1.5 秒后仍未处理,才会发出通知,因此被自动处理的审批(Auto Review 授权、其他插件)不会发出通知;请求一旦得到处理,对应的通知也会立即撤回。程序坞/任务栏角标会显示等待中的对话数量。点击通知会打开对应的对话。 | <a href="assets/features/notifications.webp"><img src="assets/features/notifications.webp" width="160" alt="截图:后台通知"></a> |
97
101
  | **空闲时打开等待中的对话** | <a id="feature-idleswitch"></a>当某个对话提出问题,或等待审批或计划审阅(橙色圆点),或完成了你尚未查看的一轮(绿色圆点),而你 30 秒没有使用 dsh 时,dsh 会打开该对话。橙色对话先于绿色对话打开。同一组中,等待最久的先打开;下一个会在再次 30 秒没有使用后打开。按键、点击、移动鼠标、滚轮或触摸都会让 30 秒重新计时。有焦点的输入框中有文字、选中了文本,或打开了菜单或对话框时,也不会切换。等待中的对话正在屏幕上时,不会切换。如果你没有回答就离开了等待中的对话,再次 30 秒没有使用后,它会再次打开。绿色对话打开后,它的圆点会消失;下一个绿色对话要等你再次使用 dsh,之后又 30 秒没有使用时才会打开。dsh 在后台时也会生效,但 dsh 不会被带到前台。 | <a href="assets/features/idleSwitch.webp"><img src="assets/features/idleSwitch.webp" width="160" alt="截图:空闲时打开等待中的对话"></a> |
98
- | **下一个等待中的对话** | <a id="feature-nextwaiting"></a>侧边栏标题栏中的按钮显示还有多少其他对话在等你,圆点颜色与下一个对话一致:它提出问题或等待审批或计划审阅时为橙色,完成了你尚未查看的一轮时为绿色。点击按钮或按 Cmd+Shift+E(Windows 和 Linux 上为 Ctrl+Shift+E)即可打开该对话。橙色对话先于绿色对话;同一组中,等待最久的对话排在前面。当前屏幕上的对话不计入。没有其他对话在等你时,按钮不显示,按键也不起作用。可在 dsh 的键盘快捷键页面(下一个等待中的对话)中更改按键。 | <a href="assets/features/nextWaiting.webp"><img src="assets/features/nextWaiting.webp" width="160" alt="截图:下一个等待中的对话"></a> |
102
+ | **下一个等待中的对话** | <a id="feature-nextwaiting"></a>侧边栏标题栏中的按钮显示还有多少其他对话在等你,圆点颜色与下一个对话一致:它提出问题或等待审批或计划审阅时为橙色,完成了你尚未查看的一轮时为绿色。点击按钮或按 Cmd+Shift+E(Windows 和 Linux 上为 Ctrl+Shift+E)即可打开该对话。点击按钮时,还会在侧边栏中找到该对话:折叠的项目会展开,列表会滚动到该对话,该对话会闪烁 5 秒。橙色对话先于绿色对话;同一组中,等待最久的对话排在前面。当前屏幕上的对话不计入。没有其他对话在等你时,按钮不显示,按键也不起作用。可在 dsh 的键盘快捷键页面(下一个等待中的对话)中更改按键。 | <a href="assets/features/nextWaiting.webp"><img src="assets/features/nextWaiting.webp" width="160" alt="截图:下一个等待中的对话"></a> |
99
103
  | **选项按钮** | <a id="feature-optionpicks"></a>当回答以选项列表结尾(“选项”标题或加粗的“选项”行下的 1 到 9 项编号列表,也包括“下一步”以及应用各语言中的同义词)时,回答下方会为每个选项显示一个按钮。点击即把“Option N: …”连同整个选项作为你的消息发送,就像你自己输入的一样。标有“(推荐)”的选项会有标记。按钮只显示在最新的回答下,且对话空闲、输入框为空时才显示;已选择过的回答和被回退撤回的回答不显示按钮。按钮显示期间,“下一条消息预测”不会给出猜测。在智能体的提问卡片上,按 1–9 键与点击效果相同,选择对应选项(在回答输入框中无效,问题出现后的前 0.8 秒内也无效)。 | <a href="assets/features/optionPicks.webp"><img src="assets/features/optionPicks.webp" width="160" alt="截图:选项按钮"></a> |
100
104
  | **应用语言** | <a id="feature-language"></a>设置 → Composer Plus → **应用语言** 可以把整个 dsh 界面切换为 Türkçe、Español、Português (Brasil)、Français、Deutsch、Русский、日本語、한국어、Bahasa Indonesia、Tiếng Việt 或 हिन्दी(见下文)。 | <a href="assets/features/language.webp"><img src="assets/features/language.webp" width="160" alt="截图:应用语言"></a> |
101
105
  | **手机连接(iPhone)** | <a id="feature-mobileconnect"></a>通过 [Tether](tether/) 应用在 iPhone 上使用 dsh,可以在同一 Wi-Fi 下使用,也可以通过 [Tailscale](https://tailscale.com) 使用。请在 **设置 → Composer Plus** 中开启它(默认关闭,因为它会在这台 Mac 上开放一个网络端口)。然后点击侧边栏左下角的 **手机连接**:用 iPhone 相机扫描二维码(轻点 **Open in Tether**),或用 Tether 中的 **Scan code** 扫描,轻点 **Connect**,然后点击 **Approve**。请先安装 Tether:没有它,相机无法打开这个二维码。手机可以显示你的对话、发送消息(可附照片:输入框的 **+** → Camera、Photos 或 Paste image)、回答审批和问题,以及切换模型和智能体。你可以配对任意多台 Mac:点击应用左上角的 Mac 名称即可在它们之间切换。**手机连接** 面板会列出已配对的 iPhone 及其上次连接时间:点击 **Remove** 可立即断开其中一部。“Tailscale only”会拒绝来自本地网络的连接(适用于共享 Wi-Fi),访问日志会显示谁尝试过连接。在 Tether 中,“Settings → Privacy”可以用 Face ID 锁定应用。 | <a href="assets/features/mobileConnect.webp"><img src="assets/features/mobileConnect.webp" width="160" alt="截图:手机连接(iPhone)"></a> |
102
- | **远程电脑** | <a id="feature-remotecomputers"></a>通过 [Tailscale](https://tailscale.com) 打开你其他 Mac 上的 dsh,并在这里继续它们的对话。在每台 Mac 的 **设置 → Composer Plus** 中开启它(默认关闭,因为它会打开网络端口)。在要被打开的 Mac 上开启 **允许其他电脑打开本机**,点击 **创建配对链接** 并复制链接。在本机对话标题左侧的电脑选择器中选择 **添加电脑…**,粘贴链接并点击 **连接**,然后在另一台 Mac 上点击 **批准**。之后选择器会列出 **本机** 和你的电脑:选择一台,它的 dsh 会在右侧边栏的浏览器标签页(或浏览器窗口)中打开,带有它自己的对话、工具和文件。设置面板可以重命名和移除电脑,并列出可以打开本机的电脑(**移除** 会立即断开其中一台)。 | <a href="assets/features/remoteComputers.webp"><img src="assets/features/remoteComputers.webp" width="160" alt="截图:远程电脑"></a> |
103
- | **自动更新** | <a id="feature-autoupdate"></a>dsh 启动时,插件会检查 [最新的 GitHub release](https://github.com/furkancak1r/dsh-composer-plus/releases/latest)。如果它比当前版本新,并且已发布到 npm,插件会通过 dsh 自带的插件管理器(与 设置 → 插件 相同的途径)安装这个确切的版本,并通知你。重启 dsh 即可加载新版本。**设置 → Composer Plus → Composer Plus 更新** 会显示版本号,并提供 **检查更新** / **立即更新**。dsh 第一次以新版本启动时,会显示一次 **Composer Plus N 的新功能** 窗口,内容是该版本的发布说明,使用 **应用语言** 中选择的语言(说明随新版本一起安装,不会额外联网;没有该语言的说明时显示英文说明);点击 **关闭** 或按 `Esc` 关闭。 通过 设置 → 插件 或复制文件夹更新后,每个新版本也会显示一次该窗口;首次安装时不显示。更新行中的 **此版本的新功能** 可再次打开它。如果磁盘上的版本比正在运行的版本新,该行会提示重启 dsh。 | <a href="assets/features/autoUpdate.webp"><img src="assets/features/autoUpdate.webp" width="160" alt="截图:自动更新"></a> |
106
+ | **远程电脑** | <a id="feature-remotecomputers"></a>通过 [Tailscale](https://tailscale.com) 打开你其他 Mac 上的 dsh,并在本 dsh 窗口中使用它们。在每台 Mac 的 **设置 → Composer Plus** 中开启它(默认关闭,因为它会打开网络端口)。在要被打开的 Mac 上开启 **允许其他电脑打开本机**,点击 **创建配对链接** 并复制链接。在本机左侧边栏的电脑选择器(品牌名右侧、**新建会话** 上方)中选择 **添加电脑…**,粘贴链接,输入 **名称**(不能与本机或其他电脑同名),点击 **连接**,然后在另一台 Mac 上点击 **批准**。之后选择器会列出 **本机** 和你的电脑:选择一台,它的 dsh 会占满本窗口,带有它自己的对话、侧边栏、工具和文件,而本机会在后台继续运行。另一台 Mac 的页面在同一位置显示同一个选择器:在那里选择 **本机** 即可返回。设置面板可以重命名和移除电脑,并列出可以打开本机的电脑(**移除** 会立即断开其中一台)。 | <a href="assets/features/remoteComputers.webp"><img src="assets/features/remoteComputers.webp" width="160" alt="截图:远程电脑"></a> |
107
+ | **自动更新** | <a id="feature-autoupdate"></a>dsh 启动时,插件会检查 [最新的 GitHub release](https://github.com/furkancak1r/dsh-composer-plus/releases/latest)。如果它比当前版本新,并且已发布到 npm,插件会通过 dsh 自带的插件管理器(与 设置 → 插件 相同的途径)安装这个确切的版本,并通知你。重启 dsh 即可加载新版本。**设置 → Composer Plus → Composer Plus 更新** 会显示版本号,并提供 **检查更新** / **立即更新**。dsh 第一次以新版本启动时,会显示一次 **Composer Plus N 的新功能** 窗口,内容是该版本的发布说明,使用 **应用语言** 中选择的语言(说明随新版本一起安装,不会额外联网;没有该语言的说明时显示英文说明);点击 **关闭** 或按 `Esc` 关闭。说明下方有一行请求在 GitHub 上加星标,最多每 30 天显示一次(不联网);点击 **在 GitHub 上加星标** 或 **不再询问** 后不再显示。 通过 设置 → 插件 或复制文件夹更新后,每个新版本也会显示一次该窗口;首次安装时不显示。更新行中的 **此版本的新功能** 可再次打开它。如果磁盘上的版本比正在运行的版本新,该行会提示重启 dsh。 | <a href="assets/features/autoUpdate.webp"><img src="assets/features/autoUpdate.webp" width="160" alt="截图:自动更新"></a> |
108
+ | **开/关筛选** | <a id="feature-featurefilter"></a>在 **设置 → Composer Plus** 的 **搜索功能** 旁边:**全部** 显示所有功能,**已开启** 只显示开关已开启的功能,**已关闭** 只显示开关已关闭的功能。依据是每一行自己的开关,也包括 dsh Host 保存的开关(例如 **手机连接**)。它与搜索一起使用:输入一个词并选择 **已关闭**,即可找到你关闭过的功能。在 **已开启** 或 **已关闭** 下切换过的行会一直保留,直到你再次选择筛选,因此焦点会停在它的开关上。**已开启** 和 **已关闭** 会隐藏 **应用语言** 和 **Composer Plus 更新** 两行(它们没有开关)。每次打开设置时都从 **全部** 开始。 | <a href="assets/features/featureFilter.webp"><img src="assets/features/featureFilter.webp" width="160" alt="截图:开/关筛选"></a> |
109
+ | **报告问题** | <a id="feature-issuereport"></a>设置 → Composer Plus → **报告问题…**(在介绍文字下方),或在侧边栏中右键点击对话(或打开它的 **…** 菜单)→ **报告 Composer Plus 问题…**。选择类型:**缺陷**、**功能请求**、**改进**、**翻译** 或 **提问**。用你自己的话和语言描述它,然后点击 **写草稿**(或按 `Cmd/Ctrl+Enter`)。调用一次 dsh 的默认模型写出英文 issue:标题带有该类型的前缀(例如 `[Bug]`),正文包含该类型的各个部分(缺陷为:What happened、Steps to reproduce、Expected、Actual),另加一行 Composer Plus、dsh 和系统的版本。阅读并修改标题和描述;**重新生成** 会写一份新草稿。**创建 issue** 会用你的 GitHub CLI(`gh`)登录在 Composer Plus 的 GitHub 仓库中创建它,并显示它的链接。issue 是公开的:看起来像密钥或密码的文字会被拒绝。`gh` 无法创建时,**改在 GitHub 上打开** 会打开 GitHub 自己的页面并填好草稿。 | <a href="assets/features/issueReport.webp"><img src="assets/features/issueReport.webp" width="160" alt="截图:报告问题"></a> |
104
110
  | **备份** | <a id="feature-backup"></a>设置 → Composer Plus → **备份** → **立即备份**:把整个 dsh 主目录(对话、设置、插件、MCP、技能)连同校验和写成 `~/Desktop/dsh-backups` 中的一个归档。归档中的机密会被遮蔽:凭据文件、密钥文件(`.pem`、`.key`)以及 yml 设置中的 token、secret 和 password 值只保存在这台电脑上,位于该文件夹的 `vault` 中。`cache/`、`dsh-runtimes/` 和 `node_modules` 不会归档(dsh 会重新生成它们)。列表中的每份备份都有 **检查**(完整读取)、**用于另一台电脑**(一个可复制到新电脑的文件夹,含救援控制台)和 **删除**。同一文件夹中 dsh-backup 的归档也会显示并可使用。要恢复,请退出 dsh 并使用救援控制台。 | <a href="assets/features/backup.webp"><img src="assets/features/backup.webp" width="160" alt="截图:备份"></a> |
105
111
  | **自动备份** | <a id="feature-autobackup"></a>打开 **自动备份**(默认关闭:每份大约与不含缓存的 dsh 主目录一样大)。选择频率(**每**:6 小时到 7 天,默认 24 小时)和保留的份数(**保留**:3 到 14 份,默认 7 份)。下次备份时间显示在开关下方;失败的备份也会显示在那里,并在 30 分钟后重试。dsh 或 Composer Plus 以新版本启动时,会在启动后不久、新版本长时间使用你的数据之前拍一份快照(保留最新的两份,标记为 **升级前**)。 | <a href="assets/features/autoBackup.webp"><img src="assets/features/autoBackup.webp" width="160" alt="截图:自动备份"></a> |
106
112
  | **会话日志检查** | <a id="feature-sessiondoctor"></a>**检查会话日志** 会读取每个对话的日志,并列出 dsh 无法打开的日志(帧损坏、记录被截断、序号错误)。尾部撕裂的日志不会列出:dsh 会自行修复。**从备份修复** 会放回最新一份含有健康副本的备份中的副本,并把损坏的文件保留在旁边(`.corrupt-<时间>`)。该备份之后写入的消息不在副本中。在此 dsh 中打开的对话不会在这里修复:请退出 dsh,在救援控制台中修复。 | <a href="assets/features/sessionDoctor.webp"><img src="assets/features/sessionDoctor.webp" width="160" alt="截图:会话日志检查"></a> |
@@ -112,7 +118,7 @@ dsh plugin --profile web add dsh-composer-plus
112
118
 
113
119
  ## 设置
114
120
 
115
- 每项功能都可以单独开启或关闭:dsh **设置 → Composer Plus**。每一行都有一张小截图:将鼠标悬停在上面可查看放大的预览,点击可按原尺寸打开(按 `Esc` 关闭)。顶部的 **搜索功能** 框会按名称或描述筛选页面(支持任何语言,不区分大小写和重音符号);按 `Esc` 可清空。每一行都有一个开关和一段简短说明;更改会立即生效,不需要重启,并会保存在该设备上。行名后面的小 **?** 会打开 README 中介绍该功能的部分,使用与 dsh 界面语言相同的 README。除 **手机连接**、**重启后继续** 和 **输入时补全** 外,所有功能默认开启。这三项以及 **内容搜索**、**回合回退**、**下一条消息预测** 和 **目标检查** 的开关由 dsh 的 Host 保存,因此它们对每个窗口都有效,并在重启后保留。**下一条消息预测**、**输入时补全**、**优化提示词** 和 **目标检查** 的 **模型** 选择也由 Host 保存(**输入时补全** 默认 **与“下一条消息预测”相同**,**优化提示词** 和 **目标检查** 默认 **与对话的模型相同**);**本对话内允许** 的 **时限**(默认 1 小时)也由 Host 保存。**内容搜索** 只有在重启 dsh 后才会改变(dsh 在启动时读取它);在此之前,它所在的行会显示 **重启 dsh 后此更改生效。**
121
+ 每项功能都可以单独开启或关闭:dsh **设置 → Composer Plus**。每一行都有一张小截图:将鼠标悬停在上面可查看放大的预览,点击可按原尺寸打开(按 `Esc` 关闭)。顶部的 **搜索功能** 框会按名称或描述筛选页面(支持任何语言,不区分大小写和重音符号);按 `Esc` 可清空。旁边的 **全部**、**已开启**、**已关闭** 分别显示所有功能、仅已开启的功能或仅已关闭的功能(关闭 **开/关筛选** 可隐藏这些按钮)。每一行都有一个开关和一段简短说明;更改会立即生效,不需要重启,并会保存在该设备上。行名后面的小 **?** 会打开 README 中介绍该功能的部分,使用与 dsh 界面语言相同的 README。除 **手机连接**、**重启后继续** 和 **输入时补全** 外,所有功能默认开启。这三项以及 **内容搜索**、**回合回退**、**下一条消息预测** 和 **目标检查** 的开关由 dsh 的 Host 保存,因此它们对每个窗口都有效,并在重启后保留。**下一条消息预测**、**输入时补全**、**优化提示词** 和 **目标检查** 的 **模型** 选择也由 Host 保存(**输入时补全** 默认 **与“下一条消息预测”相同**,**优化提示词** 和 **目标检查** 默认 **与对话的模型相同**);**本对话内允许** 的 **时限**(默认 1 小时)也由 Host 保存。**内容搜索** 只有在重启 dsh 后才会改变(dsh 在启动时读取它);在此之前,它所在的行会显示 **重启 dsh 后此更改生效。**
116
122
 
117
123
  <p align="center"><img src="docs/images/settings.webp" width="720" alt="设置 → Composer Plus:功能搜索、语言选择器、更新行和每项功能的开关,悬停时显示截图预览"></p>
118
124
 
@@ -172,6 +178,7 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
172
178
  - **内容搜索:** 没有新的代码来搜索你的对话:插件只是开启 dsh 自带的消息索引(SQLite FTS5,`dsh-session-query-sqlite`),并为它提供一个文件 `$DSH_HOME/cache/composer-plus/session-search.db`(文件和文件夹仅所有者可访问)。这个文件包含你的用户消息和助手消息的文本,因此它的私密程度与 `$DSH_HOME/sessions` 中的对话日志相同。任何数据都不会离开你的电脑。它是 dsh 可以重新生成的副本:你可以在 dsh 关闭时删除它。开关保存在 `$DSH_HOME/storages/dsh-composer-plus/content-search.json` 中。
173
179
  - **回合回退:** 在你发送每条消息之前,Host 会在它自己的存储 `$DSH_HOME/cache/composer-plus/rollback/<chat>.git`(仅所有者可访问的文件夹)中,为对话的文件夹创建一个 git 快照。快照保存该文件夹中 git 能看到的文件(不包括被忽略的文件)的副本,因此它的私密程度与项目相同。它会读取你仓库中的对象(不会再复制一份 git 已有的内容),并且从不写入你仓库的索引、`HEAD`、分支、标签、stash 或配置;git 在你的仓库中做出的唯一更改,是更新它复用的对象文件的时间戳(这会推迟 `git gc` 对这些文件的清理)。带文件的回退只会写入对话文件夹内的普通文件;它会跳过符号链接、管道以及文件夹外的任何内容。任何数据都不会离开你的电脑。你可以在 dsh 关闭时删除这个存储(此后在发送新消息之前,回退只能用于对话本身)。**删除对话** 也会删除对话的存储。开关保存在 `$DSH_HOME/storages/dsh-composer-plus/rollback.json` 中。带文件的回退还会保留回退之前那一刻的文件(同一存储中的 `refs/r/…`),供 **重新应用** 使用;已重新应用的回退记录在 `$DSH_HOME/storages/dsh-composer-plus/redone.json` 中(仅所有者可访问)。
174
180
  - **创建 GitHub issue**(功能创意 → **稍后** 列表):只在你点击时运行。Host 端会在对话的文件夹中运行你自己的 GitHub CLI(`gh issue create`),使用该创意保存的标题和概要(以及理由);`gh` 使用它自己的登录和该文件夹的 GitHub 远程仓库,因此 issue 会以你的账号创建在那个仓库中,能看到那个仓库的所有人都能看到它。dsh 会从它启动的命令中移除名称像令牌(token)的变量;如果你通过 `GH_TOKEN` 或 `GITHUB_TOKEN` 给 `gh` 提供了令牌,插件只把这两个变量传给 `gh`。issue 链接会与该创意一起保存在 `later.json` 中。
181
+ - **报告问题**(设置 → Composer Plus → **报告问题…**,或对话的 **…** 菜单 → **报告 Composer Plus 问题…**):在你点击之前,什么都不会运行。**写草稿** 只把类型和你输入的文字(不发送对话,也不发送项目)通过一次调用发送给 dsh 的默认模型。Host 端会添加一行 Composer Plus、dsh 和操作系统的版本,并把你的主目录路径替换为 `~`。**创建 issue** 会用你的 `gh` 登录运行你自己的 GitHub CLI(`gh issue create --repo furkancak1r/dsh-composer-plus`),因此 issue 是公开的,并会显示你的 GitHub 账号。看起来像密钥、令牌或密码的文字会被拒绝,不会离开你的 Mac。
175
182
  - **改动此文件的对话**(改动面板,文件的“…”菜单):Host 端按 dsh 保存的样子,读取文件夹位于同一仓库中的对话的日志,并返回每个对话的标题、时间和请求的第一行;它从不打开该文件本身。
176
183
  - **对话行显示 Git 分支:** Host 端会在每轮开始和结束时,在对话的文件夹中运行 `git branch --show-current`(只读),并只把对话 id、分支名和时间保存在 `$DSH_HOME/storages/dsh-composer-plus/session-branches.json` 中(仅所有者可访问的文件,最多保留最新的 2000 个对话)。开关保存在它旁边的 `branches.json` 中。
177
184
  - **在对话中查找** 只在页面中运行:它读取页面显示的文本,不发送任何内容。
@@ -180,9 +187,10 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
180
187
  - **本对话内允许:** 授权连同授予时间一起保存在 `$DSH_HOME/storages/dsh-composer-plus/chat-grants.json` 中(仅所有者可访问)。一项授权只覆盖一个对话中的一个确切工具,直到时限结束(默认 1 小时)。如果该工具可以写入,授权也覆盖任何写入操作。可以在标题栏的标签中撤销授权。
181
188
  - Auto Review 仍会检查每一次调用;审查失败的调用永远不会被自动允许。
182
189
  - **手机连接:** 在你开启之前一直关闭。开启后,Host 端会运行 Tether 网关:端口 19390 上的 HTTPS 服务只接受你的 Wi-Fi 子网和你的 tailnet(100.64.0.0/10);另有一个位于 `127.0.0.1:19391` 的配对页面(仅限这台 Mac)。手机需要二维码中的一次性代码(5 分钟内有效),还需要你在 Mac 上点击 **Approve**。手机会固定(pin)网关的证书。Mac 只保存每部手机令牌的哈希值(有效期 30 天,使用期间会续期);**Remove** 会立即关闭该手机的连接。手机只能调用允许列表中的 dsh 方法(对话、提示词、附件、斜杠命令、审批、问题、模型、智能体、目标),不能访问设置、凭据、文件浏览器或终端。“Tailscale only”会拒绝来自本地网络的所有连接,包括已经打开的连接。访问日志在 `access.json` 中保存最新的 200 次连接尝试(时间、IP 地址、设备名称、允许或拒绝,以及原因);其中从不包含令牌或配对代码。数据:`$DSH_HOME/storages/dsh-composer-plus/mobile/`(仅所有者可访问的文件)。详情:[tether/README.md](tether/README.md)。
183
- - **远程电脑:** 在你于每台 Mac 上开启之前一直关闭。**允许其他电脑打开本机** 会再开放一个 HTTPS 端口(19392),只在这台 Mac 的 Tailscale 地址上开放(不在 Wi-Fi 上,也不通过 Bonjour 广播)。另一台 Mac 需要一次性配对链接(5 分钟内有效)、匹配的证书(已固定)和你点击 **批准**;这台 Mac 只保存其令牌的哈希值。已配对的电脑可以完全控制这台 Mac 上的 dsh,就像你在这里使用一样:包括对话、模型、工具、文件和终端。在设置面板中移除它,它的连接会立即关闭。用来打开其他 Mac 的那台 Mac 会把对方的令牌保存在 `$DSH_HOME/storages/dsh-composer-plus/computers.json` 中(仅所有者可访问)。你每选择一台电脑,它都会在 `127.0.0.1` 上打开一个中继:页面会得到一个一次性链接(60 秒内有效),中继只接受它自己的页面(它自己的地址,不接受其他网站)。由另一台 Mac 打开的页面不显示电脑选择器,也不能更改这台 Mac 的电脑。
190
+ - **远程电脑:** 在你于每台 Mac 上开启之前一直关闭。**允许其他电脑打开本机** 会再开放一个 HTTPS 端口(19392),只在这台 Mac 的 Tailscale 地址上开放(不在 Wi-Fi 上,也不通过 Bonjour 广播)。另一台 Mac 需要一次性配对链接(5 分钟内有效)、匹配的证书(已固定)和你点击 **批准**;这台 Mac 只保存其令牌的哈希值。已配对的电脑可以完全控制这台 Mac 上的 dsh,就像你在这里使用一样:包括对话、模型、工具、文件和终端。在设置面板中移除它,它的连接会立即关闭。用来打开其他 Mac 的那台 Mac 会把对方的令牌保存在 `$DSH_HOME/storages/dsh-composer-plus/computers.json` 中(仅所有者可访问)。你每选择一台电脑,它都会在 `127.0.0.1` 上打开一个中继:页面会得到一个一次性链接(60 秒内有效),中继只接受它自己的页面(它自己的地址,不接受其他网站)。本窗口中另一台 Mac 的页面可以读取这台 Mac 的电脑名称和状态(不能读取地址和令牌),并可以在它们之间切换本窗口;它不能添加、重命名或移除电脑,也不能更改这台 Mac 的设置。
184
191
  - **更新:** Host 端会向 `api.github.com` 查询最新版本,并向 `registry.npmjs.org` 查询这个确切的版本;不会发送任何关于你或你的对话的信息。安装使用 dsh 的插件管理器(`pnpm add dsh-composer-plus@<version>`;如果 dsh 0.2.1 对已安装的插件拒绝这样做,则使用 `pnpm add https://registry.npmjs.org/dsh-composer-plus/-/dsh-composer-plus-<version>.tgz`,之后 profile 会把这个 URL 列为插件的来源),因此 dsh 自己的 registry、脚本和版本兼容性检查都会生效。预发布标签和旧版本永远不会被安装。
185
- - **使用计数:** Host 端每天一次向 GitHub 请求已安装版本对应 release 中的小文件 `active.txt`(`github.com/furkancak1r/dsh-composer-plus/releases/download/v<version>/active.txt`)。GitHub 按文件统计这些请求,因此作者只能看到当天每个版本有多少份副本发出了请求。请求中没有 ID、Cookie,也没有关于你或你的对话的任何数据;和每次检查更新一样,GitHub 能看到你的 IP 地址。最后一次请求的日期保存在 `$DSH_HOME/storages/dsh-composer-plus/active.json`。本仓库的 git 检出副本和改名的副本不会发送请求。此功能没有开关。
192
+ - **土耳其语技能包:** 默认开启,且只在应用语言为 Türkçe 时工作:浏览器会把 dsh 的界面语言告诉 Host 端。之后 Host 端最多每天一次向 `api.github.com` 查询 `yucezerey/turkish-anti-ai-skill` 的最新提交及其文件列表,并从 `raw.githubusercontent.com` 下载有变化的 Markdown 文件。与每次检查更新一样,GitHub 能看到你的 IP 地址;不会发送任何关于你或你的对话的信息。只保留 `skills/turkish-anti-ai-skill/` 文件夹中的普通 `.md` 文件(最多 20 个文件、1 MB),每个文件都必须与 GitHub 文件列表中的 ID 一致。副本位于 `$DSH_HOME/cache/composer-plus/turkish-skill/<commit>`(仅所有者可访问),并加入 dsh 的技能列表;技能包中的脚本不会运行。开关和最后报告的语言保存在 `$DSH_HOME/storages/dsh-composer-plus/turkish-skill.json`。关闭后会移除该技能并删除副本。
193
+ - **使用计数:** Host 端向 GitHub 请求已安装版本对应 release 中的两个小文件(`github.com/furkancak1r/dsh-composer-plus/releases/download/v<version>/<file>`):`active.txt` 每天一次,`installed.txt` 每个 dsh 主目录只请求一次。GitHub 按文件统计这些请求,因此作者只能看到当天每个版本有多少份副本请求了 `active.txt`,以及总共有多少份副本请求了 `installed.txt`(副本数)。请求中没有 ID、Cookie,也没有关于你或你的对话的任何数据;和每次检查更新一样,GitHub 能看到你的 IP 地址。最后一次请求 `active.txt` 的日期保存在 `$DSH_HOME/storages/dsh-composer-plus/active.json`,`installed.txt` 已被计数的标记保存在 `$DSH_HOME/storages/dsh-composer-plus/installed.json`。本仓库的 git 检出副本和改名的副本不会发送请求。此功能没有开关。
186
194
  - **下一条消息预测:** 默认开启;在 **设置 → Composer Plus** 中关闭它,即可停止这些模型调用。每完成一个回合,Host 端会把该对话中你自己的消息和智能体的文字回复(不含工具输出、隐藏上下文和推理内容),最多约 6,000 个字符(最新的部分),发送给所选的模型(除非你另选,否则为 dsh 的默认模型)。它通过 dsh 自己的提供商设置,以一次请求的形式发送。对话中不会保存任何内容:猜测不会被添加到对话历史中。开关和模型选择保存在 `$DSH_HOME/storages/dsh-composer-plus/predict.json` 中(仅所有者可访问的文件)。
187
195
  - **输入时补全:** 在你开启之前一直关闭。开启后,你每次输入停顿时,Host 端会把你目前已输入的文字,以及该对话中你自己的消息和智能体的文字回复(不含工具输出、隐藏上下文和推理内容;对话部分最多约 3,000 个字符),发送给在 **输入时补全** 下选择的模型(默认为 **下一条消息预测** 的模型)。新的停顿会停止较早的请求。对话中不会保存任何内容。开关保存在 `$DSH_HOME/storages/dsh-composer-plus/complete.json` 中(仅所有者可访问的文件)。
188
196
  - **优化提示词:** 在你点击魔法棒之前,什么都不会运行。点击后,Host 端会以一次请求的形式,把以下内容发送给在 **优化提示词** 下选择的模型(默认为对话自己的模型):你的草稿;该对话中你自己的消息和智能体的文字回复(不含工具输出、隐藏上下文、推理内容和已回退的消息;最多约 6,000 个字符,最新的部分);与 **功能创意** 相同的项目概要(文件规则也相同:不读取 `.env*` 或密钥文件,跳过指向文件夹外部的符号链接和 FIFO);`git status` 列为已更改的路径;以及最多 30 个名称与你的草稿有相同词语的已跟踪或未跟踪(未被忽略)路径。除这份概要外,不会发送任何文件内容。对话和项目中都不会写入任何内容:回答只会替换输入框中的草稿,是否发送由你决定。模型选择保存在 `$DSH_HOME/storages/dsh-composer-plus/improve.json` 中(仅所有者可访问的文件)。
@@ -212,10 +220,11 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
212
220
  - **重启后对话没有继续:** dsh 退出时“重启后继续”处于关闭状态;或者你自己停止了那个回合;或者回合已经结束(完成或失败);或者对话已被归档;或者当时有超过 20 个对话正在运行(只有最新的 20 个会继续)。
213
221
  - **重启后队列是空的:** 清空队列的是 dsh 自己的退出流程。重启后继续只会在它继续的对话中放回队列;其他对话请重新发送消息。退出前刚删除的消息可能会回来:请再删除一次。
214
222
  - **创建 GitHub issue 提示“gh could not open the issue”:** 这条消息是 `gh` 自己输出的第一行。请在终端中运行 `gh auth status`,并用 `gh auth login` 登录;对话的文件夹必须有 GitHub 远程仓库(`git remote -v`)。“not installed or not on the PATH of dsh”表示 dsh 没有找到 `gh`:请安装它,或从一个能运行 `gh` 的终端启动 dsh。
223
+ - **报告问题提示“gh could not open the issue”:** 这条消息是 `gh` 自己输出的第一行。请在终端中运行 `gh auth status`,并用 `gh auth login` 登录。或者点击 **改在 GitHub 上打开**:GitHub 自己的新建 issue 页面会打开并填好草稿,你在那里提交即可。
215
224
  - **对话行上没有分支标签:** 侧边栏不是“单列表”视图(在“按工作区”和“按工作区树”中,对话行都不显示标签);或者安装此版本后,该对话还没有新的一轮;或者它的文件夹不是 git 仓库;或者 **对话行显示 Git 分支** 已关闭。更新后,请重启 dsh(分支由 Host 端记录)。
216
225
  - **目标检查显示测试以代码 127 或 “not found” 退出:** 从 Finder 或程序坞启动的 dsh 的 PATH 可能比终端里的短,因此找不到 `npm`、`pnpm` 或 `cargo`。请从终端启动 dsh,或者在 **目标检查** → **编辑** 中用完整路径设置你自己的测试命令。**立即运行** 会马上显示结果。
217
226
  - **目标没有关闭,智能体一直在工作:** **目标检查** 发现了失败的测试或缺少的工作;目标下方的一行会显示原因(将鼠标悬停在结果上可查看详情)。请修正原因或测试命令,或者用 `/goal` 或目标栏自己完成目标(这不会被检查)。
218
- - **刚发布新版本时安装失败:** 请加上版本号(`dsh-composer-plus@1.22.0`),见上文的安装说明。
227
+ - **刚发布新版本时安装失败:** 请加上版本号(`dsh-composer-plus@1.23.0`),见上文的安装说明。
219
228
  - **立即备份显示“另一个备份操作正在进行”:** 另一个 dsh 窗口或救援控制台中正在运行一个操作(它们共用备份文件夹中的锁文件 `.composer-plus.lock`)。请等待它完成。如果没有操作在运行,下次点击时会接管已关闭进程留下的锁。
220
229
  - **救援控制台显示“dsh is running”:** 请先退出 dsh。如果 dsh 确实已关闭,消息中提到的文件是崩溃后留下的:删除它,然后重试。
221
230
  - **恢复后 dsh 无法启动或缺少插件:** 需要重新安装 profile 的插件包。恢复后,救援控制台会提供这一步;你也可以在它列出的 `$DSH_HOME/profiles/` 下的每个文件夹中运行 `pnpm install`。不在 vault 中的机密(另一台电脑)必须重新输入:控制台会列出它们。
@@ -237,9 +246,11 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
237
246
  - 如果新版本不改动任何 dsh 设置,dsh 0.2.1 会立即加载更新;否则(以及在 dsh 0.2.0 上),请重启 dsh 来加载更新。插件从不替你重启 dsh。没有 dsh 插件管理器的网页版/CLI profile 只会提示有新版本。
238
247
  - **Composer Plus N 的新功能** 使用 **应用语言** 中的语言显示说明。带其他地区的界面语言使用其基础语言的说明(`en-GB` 显示英文说明,`zh-TW` 显示 `zh` 的中文说明)。没有说明的语言(由其他语言包添加的语言,或不是 `pt-BR` 的 `pt-PT`)显示英文说明。说明随新版本一起安装。如果运行的副本没有可读的说明文件(`release-notes.json`),窗口会显示 GitHub 上的英文说明。
239
248
  - dsh 运行时安装的版本(通过 设置 → 插件 或复制的文件夹更新)要在重启 dsh 后才会加载:在此之前,**Composer Plus 更新** 显示正在运行的版本并提示重启。该版本的 **Composer Plus N 的新功能** 窗口在这个版本第一次启动时显示一次。首次安装本插件后的第一次启动不显示窗口;**此版本的新功能** 可打开正在运行的版本的说明。
249
+ - 说明下方的 GitHub 星标请求无法知道你是否已经加了星标:插件不会为此联网。每个浏览器(以及桌面应用)各自记录,因此在另一个浏览器中这一行可能会再显示一次。
240
250
  - **拒绝并附言** 会先把附言作为一条消息发进正在等待的这一轮,然后拒绝该请求;dsh 会在对话中把附言显示为你的消息。附言输入框打开时,卡片上只显示 **取消** 和 **拒绝**,所以一次点击不会误允许该请求;`Esc` 或 **取消** 会让 dsh 的按钮重新出现。空附言不会发送任何内容(请使用 dsh 自己的 **拒绝**)。如果附言发出后拒绝失败(例如该请求在此期间已在别处被回答),附言会留在对话中,模型会在它的下一步读到它。按钮放在 dsh 审批卡片的标记(`data-approval-key`)中,因为 dsh 没有给卡片操作的插槽;如果 dsh 更新改变了这部分标记,按钮就不会显示,dsh 自己的 **拒绝** 照常可用。一条附言最多 4,000 个字符。
241
251
  - **密钥输入** 只隐藏值的完整原文:同一对话之后的工具结果中,它会被替换为 `[secret]`。但代理仍然可以用 shell 命令读取文件,并以其他形式(base64、其中一部分、加入空格)输出该值,这种形式会到达模型。代理自己的文件工具(read、grep、write、edit)无法打开密钥文件夹。如果该值同时也是常见词,该对话的其他结果中也会显示 `[secret]`。文件会保留到这一轮结束;如果 dsh 在这一轮中退出,下次启动时会删除它。每个对话同一时间只能有一个未完成的请求。该工具会给每次模型请求增加约 870 个字符(估计约 220 个 token):不用时请关闭。
242
252
  - **引用回复** 引用的是页面上显示的文字:代码块、表格或列表会变成普通的行,不带 Markdown 标记。如果选区从一条回答开始、在另一条回答结束,或者碰到思考过程、工具行或你自己的消息,就不会显示按钮:请只在一条回答内选择。按钮通过 dsh 的对话结构找到回答,因为 dsh 没有用于文字选区的插槽;如果 dsh 更新改变了这种结构,按钮就不会显示。如果引用位于输入框末尾,后面会多加一个空行,让你接着输入的内容从下一行开始;dsh 发送时会去掉末尾的空行。
253
+ - **从文件预览中引用** 读取的是 dsh 自己的文件预览:行号来自预览中的各行,所以代码和文本文件的行号是正确的。Markdown 文件显示为排版后的页面,所以它的引用是不带 Markdown 标记的文字,也没有行号;PDF 的引用给出页码,而不是行号。如果选区在路径标题中、在其他查看器中(表格、Office 文件、图片),或者跨越两个打开的文件,就不会显示按钮;右侧边栏全屏时也不会显示(这时输入框不在屏幕上)。按钮通过 dsh 的页面结构找到预览,因为 dsh 在那里没有用于选区的插槽;如果 dsh 更新改变了这种结构,按钮就不会显示。只有选中的文字会进入输入框:之后文件改变时,引用不会跟着改变。
243
254
  - **旁支对话** 的只读依靠 dsh 自己的只读模式:文件工具不能写入,shell 命令在不可写的 dsh 沙箱中运行(沙箱无法启动时不会运行)。代理仍可以通过 dsh 的审批卡请求更多权限,网络调用(例如 API 请求)仍可能改变计算机之外的东西。副本取自队列中没有消息等待的最新时刻,所以在有排队消息时提出的问题看不到之后发生的事。对话中正在运行的步骤在副本中会被关闭,所以旁支对话知道该步骤的开始,但不知道它的结果。旁支对话是普通对话:它留在侧边栏中并计入你的模型用量,可以在那里归档。“带回”只取旁支对话自己的消息(最多 12,000 个字符),每个问题只取最后一条回复;代理在工具调用之间的说明不会带回。dsh 没有让插件在右侧边栏中显示对话的插槽,所以旁支对话在主视图中打开。
244
255
  - **差异批注** 只在改动面板中可用,不能用于 dsh 自己的“上一轮”视图。范围必须在差异的同一个区块内(同一个 `@@` 部分)。批注保存在此浏览器的存储中,所以其他浏览器或其他电脑上的 dsh 看不到;Tether 在内存中保留自己的批注。批注保留你点击时的那些行:代理之后修改文件时,批注仍显示旧的行和行号。每个对话最多 50 条批注,每条最多 2,000 个字符、40 行(超出部分以 `…` 行表示)。
245
256
  - **本对话内允许** 从授权的那一刻开始计算时限。新的时限会立即应用于仍在生效的授权,因此更短的时限可能会让它们立即结束。1.12 及更早版本的授权没有时间信息:更新后,代理会就这些工具再询问一次。
@@ -247,7 +258,7 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
247
258
  - **问题卡片适应窗口** 只改变 dsh 的问题卡片,不改变计划审阅卡片。它通过 dsh 的标记(`data-question-key`、`data-question-scroll`)找到卡片;如果 dsh 更新改变了这些标记,卡片会按 dsh 自己的样子显示。卡片高度跟随窗口,而不是对话:在较窄侧边面板中的对话里,较高的卡片仍可能超出对话上方。
248
259
  - **对话标题栏适应窗口** 通过 dsh 生成的类名(`_titleRow`、`_titleCluster`、`_crumbs`、`_headerActions`、`_headerUtilities`)找到对话标题栏,因为 dsh 没有为它提供布局接口。如果 dsh 更新改了这些类名,标题栏按 dsh 原样显示。在很窄的对话栏中,标题栏可能占两到三行,所以消息上方的空间会变小。
249
260
  - **手机连接** 只适用于 macOS(它使用 `en0`、`openssl`、`swift` 和 `dns-sd`)。它使用固定端口 19390 和 19391,因此每台 Mac 上同一时间只能有一个 dsh 运行它(每台 Mac 是手机 Mac 列表中的一项)。在 `apns.json` 中配置 APNs 密钥后(见 `tether/README.md`),Mac 通过 Apple 推送服务发送通知,即使 Tether 已关闭也能收到;Mac 必须处于唤醒状态并打开 dsh。没有该密钥时,只有在 Tether 打开或刚转入后台时,手机才能收到通知。在 Tether 中打开“Settings → Notifications → Send notifications”之前,Tether 不会发送通知。Tether 为每台已配对的 Mac 分别保存此开关和时间段。此开关在每台 Mac 上都默认关闭,打开时 iOS 会请求通知权限。iOS 应用尚未上架 App Store:请用 Xcode 从 `tether/ios` 构建它。“Tailscale only”仍然需要 Mac 有 Wi-Fi 或以太网地址(只有在有这样的地址时,网关才会启动)。访问日志按 Mac 分别保存,只保留最新的 200 次尝试;一分钟内重复的尝试不会新增一行,而是把同一行的次数加一。每条通知都会显示 Mac 和对话所属的项目(`Studio Mac · recipe-box`)、对话标题和正文:Turn finished 显示智能体的最后一条回复。有 APNs 密钥时,这段文字会经过 Apple 的推送服务。Tether 的应用锁不会隐藏 iPhone 锁定屏幕上的通知预览:请在 iOS 设置中关闭它们。Tether 的绿点跟随 Mac:在 dsh 中打开的对话,手机上的绿点也会消失。在 Tether 中打开的对话,只在手机上消失;Mac 上 dsh 自己的绿点会一直保留,直到你在 Mac 上打开该对话。
250
- - **远程电脑** 需要两台 Mac 都有 Tailscale,并且被打开的 Mac 上要有开启了此功能的 Composer Plus。那台 Mac 必须处于唤醒状态,并且正在运行 dsh。另一台电脑会在屏幕上当前对话的右侧边栏浏览器标签页中打开:这个标签页属于该对话,所以其他对话会显示自己的侧边栏。没有对话时(或在没有浏览器标签页的 dsh web 中),电脑会在浏览器窗口中打开。只有主区域显示对话或新对话界面时,才会显示选择器;任务看板上不显示。它使用端口 19392(每台 Mac 上只有一个 dsh 能让其他电脑进入)。在另一台电脑的页面中打开的电脑没有选择器,所以你不能从那里打开第三台 Mac。
261
+ - **远程电脑** 需要两台 Mac 都有 Tailscale,并且被打开的 Mac 上要有开启了此功能的 Composer Plus。那台 Mac 必须处于唤醒状态,并且正在运行 dsh。只有左侧边栏展开时才显示选择器(图标栏中不显示)。只有当另一台 Mac 装有此版本或更新版本的 Composer Plus 时,它的页面才显示选择器;版本较旧时,几秒后顶部会出现 **返回本机** 按钮。你打开过的每台电脑的页面会一直保留在内存中,直到你关闭此功能或重启 dsh。在桌面应用中,另一台 Mac 的页面是嵌入的页面:dsh 自己的键盘快捷键(例如新建对话)在其中不起作用,那里也无法下载文件、使用麦克风或通知。它使用端口 19392(每台 Mac 上只有一个 dsh 能让其他电脑进入)。另一台 Mac 页面中的选择器只在这台 Mac 的电脑之间切换,所以你不能通过另一台 Mac 打开第三台 Mac。
251
262
  - 会话链接在对话中有效;点击它不会打开对应的对话。用 **复制消息链接** 复制的链接,只在 dsh 0.2.1 及更高版本中、并且只从对话中的消息里才能打开对应的消息(dsh 0.2.0 没有用户文本规则,所以链接仍会被复制,但只显示为普通文本)。被回退移除的消息,或被压缩从 dsh 加载的历史中移除的消息,无法显示:此时链接会给出提示。🔗 按钮依附在 dsh 的消息行上;如果 dsh 更新改变了这部分标记,按钮就不会显示。
252
263
  - **全部标为已读** 通过 dsh 中一个不是公开 API 的部分(dsh 会话状态中的 `completionUnread` 列表)来更改 dsh 自己的未读圆点。如果 dsh 更新重命名了它,该菜单项就不会显示。该菜单项放在 dsh 项目的“…”菜单中,而这个菜单没有给插件的插槽;如果 dsh 修改了这个菜单,该菜单项也不会显示。
253
264
  - **原地编辑排队消息** 不会修改在此期间已经开始发送的消息,也不会覆盖在此期间已被修改的消息(例如在 iPhone 上的 Tether 中修改的),并会显示提示说明这一点。新图片需要支持图片的模型(dsh 自身的图片限制同样适用),新文件必须上传完成后才能保存。只能编辑你自己的排队消息,不能编辑队列中的目标轮次或对问题的回答。插件更新后,只有纯文字的编辑能立即生效:保存带图片或文件的消息之前,请重启 dsh(Host 端在启动时加载)。
@@ -255,6 +266,7 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
255
266
  - **树** 分支图对每个箭头的指向是推测出来的,因为 git 不保存一个分支被合并进了哪个分支。已合并的分支指向最先用合并提交接收它的分支。等待中的分支指向它分出的最近的分支(优先主分支);如果两个分支从同一个提交分出,分支图可能会选错。通过快进(fast-forward)前移的分支没有合并提交,因此它的 **已合并** 标签不会打开任何内容。主分支是 `origin` 的默认分支,否则是 `main` 或 `master`,再否则是当前打开的分支。分支图读取最新的 20,000 个提交,最多显示 60 个分支;历史更长时,它会提示有些箭头可能不对。用 squash 或 rebase 合并的分支(例如在 GitHub 上)会显示为 **等待中**:git 不保留它与其提交之间的关联。
256
267
  - **本对话** 只统计 dsh 的文件工具(`write`、`edit`、`str_replace_editor`)写入的文件。命令(`bash`)更改的文件不在这个视图中;它们显示在 **未提交** 下。如果没有该对话的回合回退快照(回合回退已关闭、安装了 dsh-rewind-plugin,或对话比插件更早),视图会与最后一次提交比较,并给出提示。回合回退为每个对话保留最新的 50 个快照:在更长的对话中,视图与保留的最早快照比较,说明从哪条消息开始,并且不计入更早的改动。**隐藏空白改动** 就是 `git diff -w`:只在行内空格上有变化的行也会被隐藏。
257
268
  - **创建 GitHub issue** 需要 dsh 的 PATH 中有 GitHub CLI(`gh`),并且对话的文件夹中有 GitHub 远程仓库。它最多运行 60 秒;如果 `gh` 用时更长,issue 可能已经在 GitHub 上创建,但列表中没有它的链接(再次点击之前,请先到 GitHub 上查看:列表只从已完成的运行中读取链接,所以再次点击可能会创建第二个 issue)。有多个 GitHub 远程仓库的文件夹使用 `gh` 自己的默认仓库(`gh repo set-default`)。
269
+ - **报告问题** 只在 Composer Plus 仓库(`furkancak1r/dsh-composer-plus`)中创建 issue,不会在你的项目中创建(要在项目中创建,请使用功能创意 **稍后** 列表中的 **创建 GitHub issue**)。写草稿需要 dsh 中有默认模型;创建 issue 需要 GitHub CLI(`gh`)并已登录。草稿总是英文。模型可能写错某个细节:点击 **创建 issue** 之前,请先阅读草稿。创建最多运行 60 秒;一小时内再次使用相同的标题和文字时,会显示之前的 issue,而不是创建副本。**改在 GitHub 上打开** 会截短很长的描述,使链接保持简短。
258
270
  - **改动此文件的对话** 与 **本对话** 一样,只识别 dsh 的文件工具(`write`、`edit`、`str_replace_editor`):命令(`bash`)做出的改动不会列出。它读取该仓库最新的 300 个对话(按创建时间),对话更多时会给出提示;它最多显示 100 行。重命名的文件按工具写入时的名称列出。文件夹在仓库之外的对话(包括通过另一条路径指向相同文件的情况)不计入。dsh 启动后第一次打开时,会读取该仓库的所有对话日志(275 个对话约需 4 秒);之后再打开时,只读取此后有变化的对话。
259
271
  - **对话行显示 Git 分支** 显示的是对话最后一轮开始或结束时其文件夹所在的分支,而不是当前的分支:在回合之外切换分支,要到下一轮之后才会显示。此版本之前的对话,在下一轮之前不显示标签。标签放在 dsh 的对话行标记中(dsh 在那里没有插槽);如果 dsh 更新改变了这部分标记,就不会显示标签。标签只在“单列表”中显示。
260
272
  - **每个对话一个分支** 在计划模式下:dsh 把 `/plan <消息>` 作为命令而不是消息发送,因此 Composer Plus 会在 dsh 的输入框中接管该命令的提交(不是公开 API)。如果 dsh 更新改变了这一点,选择 **新分支** 时的 `/plan <消息>` 会在已打开的新对话中作为 dsh 自己的命令运行。当 Host 无法在新对话中开启计划模式时,分支文件夹会保留,消息不会发送,因此新对话不会在默认模式下修改文件。
@@ -279,6 +291,8 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
279
291
  - **在新对话中继续** 会把对话(消息、工具调用以及每个工具结果的末尾,最多约 60,000 个字符,最新的部分)发送给对话自己的模型。交接说明可能漏掉某个决定或文件:开始前请先阅读。新对话只知道这份交接说明和链接的对话(dsh 会把旧对话作为只读上下文附加进来);它的模型上下文从很小开始,旧对话保持不变。每个对话同一时间只能进行一次写说明或开始操作。
280
292
  - **上下文明细** 按 dsh 自己的规则显示估算值(每个 token 约 4 个字符),与 dsh 的上下文环使用相同的规则;模型自己的计数可能不同。工具定义取自最新的一次请求。它按对话日志中的样子显示上下文;正在运行的回合会在它的下一步之后出现。
281
293
  - **计划步骤** 只有在模型确实做了计划时才会节省令牌:开启后,每个请求都会带上 `plan_next_steps` 工具和一段简短说明(约 500 个令牌),即使在模型从不做计划的对话中也是如此。条件只证明它所写的内容(例如:构建的退出码为 0),并不证明工作正确。`ok` 读取 dsh 自己的结果标记(`[exit code: N]`、超时、沙箱拒绝);一个失败但退出码不为非零的命令会被视为通过。步骤一次只运行一个;同时就绪的两个步骤会交回给模型。该步骤的消息会作为对话模型的助手消息保存(dsh 没有标记插件所写消息的方式),因此它的文字会说明是 Composer Plus 运行的。Tether 中没有此功能。
294
+ - **土耳其语技能包** 安装的是第三方技能:规则来自作者的 GitHub 仓库,而不是 Composer Plus;新版本会在下一次每日检查后直接使用,我们不会先审核。校验只能证明文件就是 GitHub 为该提交列出的文件,不能证明规则本身是好的。何时加载该技能由模型决定(你也可以按名称调用);这些规则只适用于土耳其语文本。Host 端使用浏览器最后报告的语言:两个浏览器使用不同语言时,以最新的报告为准。未登录时 GitHub 每小时只允许 60 次 API 请求;检查被拒绝时会保留已安装的副本,并在设置中显示原因。
295
+ - **理清代码库** 附带的是该技能的固定副本(caldoai/skills,提交 f3cf477):上游的改动只会随新的 Composer Plus 版本到来。该技能会运行一个较长的目标,可能改动很多文件,它的子智能体和检查也会消耗大量令牌。项目没有测试的地方,它可能在你不知情时改变行为:请在单独的分支上启动,合并前先阅读差异。项目中、自定义技能文件夹中或 `~/.dsh/skills` 中的同名技能会取代它。开关关闭时,`/untangle-codebase` 会作为普通文本发给模型。
282
296
  - **任务看板** 显示 dsh 在此窗口中列出的对话:不在项目中的对话只显示在**所有项目**中,“每个对话一个分支”的文件夹(`<project>/.worktrees/<name>`)算作它所属的项目。卡片所在的列就是对话的实时状态,因此卡片不能拖动:对话的回合结束时(包括出错或停止之后)会移到**已完成**,再次运行时会回到**进行中**。绿色圆点是 dsh 自己的完成圆点,dsh 只在此窗口中保留它:重新加载后,已完成的对话不再显示圆点。**已完成** 显示最新的 50 个对话(**显示更早的 N 个** 显示其余对话)。**搜索项目** 只按名称查找项目,不按文件夹路径查找。当项目的所有对话都超过 2 小时、处于空闲且已读时,**最近 2 小时内活跃** 会隐藏该项目;看板仍会显示你选择的项目。**新建任务** 只记住此窗口中最新的一个新对话:如果你在发出第一条消息前打开了另一个对话,该对话会以 **新建任务** 留在 **进行中** 的最上方,直到你在其中写消息,但只有最新的那个对话会自动回到看板。重新加载后,没有消息的新对话不会出现在看板上(dsh 把它保留为自己的空白新会话)。标签来自每个任务根据其第一条消息进行的一次模型调用:它们可能不准确,不是在看板上开始的对话没有标签。标题是 dsh 自己的对话标题(由 dsh 的标题服务使用对话的模型生成);标题出现前,卡片显示第一条消息的第一行。**×** 和 **全部清除** 只把卡片从看板移除:不会删除任何对话。**撤销** 只显示 8 秒,并且只在此窗口中;之后,**已移除 (N)** → **放回** 可让卡片回来,再次运行的对话也会自动回来。**全部清除** 会先询问,然后移除所显示项目中已读的已完成卡片(选择 **所有项目** 时为所有项目),也包括 **显示更早的 N 个** 后面的卡片;未读(绿色)卡片会保留。dsh 只在此窗口中保留绿色圆点,因此重新加载后,全部清除可能会移除你还没读过回答的卡片。这个列表保存在这台 Mac 上,对每个窗口和 Tether 都有效;列表达到 5,000 个对话时,最早的对话会回来。**回答** 只回答对话最新的请求:如果对话已在别处(另一个窗口、Tether、Auto Review)回答了它,或提出了新的问题,面板会说明这一点,并且不发送任何内容。早期版本有带评论和智能体工具的任务列表;它们已被移除,旧的 `board.json` 不会显示。创意和外观来自 emanuele-em 的 [Kanzo](https://github.com/emanuele-em/kanzo)(AGPL-3.0);Composer Plus 没有使用它的任何代码。 `Cmd+Shift+K` 是 dsh 快捷键:在 Linux 的浏览器中,它没有默认按键,需要你在 dsh 的键盘快捷键页面中设置;浏览器扩展或其他应用也可能先占用它。卡片在 dsh 自己的绿点消失时(你打开了对话)离开**已完成**;对于在看板后面作为当前对话完成的对话,则在它显示且上面没有面板时离开。这个已读状态和 dsh 自己的圆点一样只保存在当前 dsh 窗口中:重新加载后或在另一个窗口中,所有已完成的对话都算已读,因此都在**最近完成**中。
283
297
  - **停止后发送的消息优先** 只在你停止对话后生效(或在某一轮失败后,或打开仍有队列的对话时):对话运行时发送的消息仍会排在队列末尾。它只会越过你自己的排队消息:如果队列中有目标轮次或问题回复,仍保持 dsh 自己的顺序。它会读取 dsh 中一个非公开 API 的部分(被停止的那一轮是否仍在结束中);如果 dsh 更新后改了它的名称,消息要等那一轮结束后才会被移到前面。子代理对话保持 dsh 自己的顺序。
284
298
  - **项目记忆** 属于对话的第一个文件夹(dsh 的项目文件夹):用 `cd` 更换文件夹的对话仍使用第一个项目的记忆;通过另一条路径打开的同一文件夹共用同一份记忆。对话开始时会冻结其记忆部分,所以之后保存的笔记从下一个对话开始显示(`memory_recall` 能立即找到)。整理只保留模型决定保留的内容:它可能漏掉某个事实,也可能保留错误的内容;请在 **打开记忆** 中检查并在那里修改,或在 dsh 关闭时手动编辑文件。**打开记忆** 会把所选项目的笔记和全局笔记(选择“全部笔记”时为所有项目;最多约 60,000 个字符,其余只计数,在该次请求中无法修改)以及你的文字发送给 dsh 的默认模型,而不是对话的模型。预览在 Mac 上保留 30 分钟,直到 dsh 重启;如果在此期间整理或智能体修改了笔记,**应用** 会拒绝,你需要重新预览。**应用** 要么保存预览中的全部修改,要么一项也不保存:只要有一项修改无法保存(例如 MEMORY.md 已满),笔记就保持原样,并显示原因。每个包含你的消息的回合结束后都会调用一次模型;调用失败时,下一个回合后会再次读取同样的消息。子智能体对话不做整理。这个思路和文件格式来自 [DS Bot](https://github.com/Fei2411/DS-Bot)(Apache-2.0);Composer Plus 没有使用它的代码,也从不读取或写入 DS Bot 自己的记忆。
@@ -293,6 +307,8 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
293
307
  - **恢复** 只在 dsh 关闭时于救援控制台中运行:它会替换整个 dsh 主目录。旧目录会以 `.dsh.pre-restore-<time>` 保留在旁边,直到你删除它,因此磁盘需要能容纳两者。对话保留其项目路径:在另一台电脑上,位于其他路径的项目会显示为缺失,直到你重新添加它。指向 dsh 主目录之外的符号链接(例如从其他文件夹链接的技能)会作为链接恢复;它们的目标必须存在于那台电脑上。
294
308
  - **会话日志检查** 按照 dsh 自己判断能否打开的规则读取每个对话的最新日志:不是 JSON 或顺序错乱的行,只有在其后还有回合结束时才算损坏(否则 dsh 会打开该对话,只是不含从该行起的各行,因此不会列出它)。它不会按照 dsh 自己的事件规则检查日志中的事件,因此被判为健康的日志在 dsh 升级后仍可能无法打开。修复会从一份备份中取回副本:该备份之后的消息会从该对话中丢失(损坏的文件保留在旁边)。在此 dsh 中打开的对话不会在设置中修复:dsh 正持有它。
295
309
  - **自动备份** 只在 dsh 运行时运行:dsh 关闭期间到期的备份,会在下次启动后约 45 秒运行。新版本的快照在那次启动之后拍摄,而不是在更新之前。
310
+ - **记住窗口大小** 仅在桌面应用中有效。每次启动时,dsh 先以 1280 × 820 显示窗口;插件加载后,窗口才移到保存的大小。页面没有窗口移动的事件,所以插件每秒读取一次窗口:dsh 退出前最后 2 秒内的更改不会被保存。大小按电脑保存在应用自己的存储中。
311
+ - **使用计数:** 统计的是副本,不是人:每个 dsh 主目录算一份副本。唯一副本数(`installed.txt`)只包含更新到第一个带有此文件的版本或更新版本的副本;停留在更旧版本的副本不会被统计。删除了 `storages` 文件夹的副本会被再次统计。GitHub 可能会把约 10 分钟内来自同一 IP 地址(例如同一个 NAT 路由器后面的多台电脑)的请求只算作一次。
296
312
  - 已向上游请求内置支持:队列 `move` 见 [#773](https://github.com/deepseek-ai/deepseek-harness/discussions/773),“继续”状态见 [#9269](https://github.com/deepseek-ai/deepseek-harness/discussions/9269)。
297
313
 
298
314
  ## 常见问题
@@ -363,6 +379,8 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
363
379
 
364
380
  **如何只回复长回答中的一部分?** 选中那一部分,然后点击选区上方的 **引用**(**引用回复**,默认开启)。这部分会以 `> …` 引用的形式进入输入框,你在下面写回复即可。
365
381
 
382
+ **如何把文件中的确切几行指给智能体?** 在右侧边栏打开这个文件(**工作区文件**),选中这些行,然后点击选区上方的 **引用**(**从文件预览中引用**,默认开启)。输入框会得到路径和行号(例如 `src/login.js:2-3`),这些行放在代码块中;在下面写你的问题即可。
383
+
366
384
  **能在代理工作时提问而不打断它吗?** 输入问题,然后点击输入框中的 **在旁支对话中提问**(**旁支对话**,默认开启)。此对话的只读副本会回答,而原对话继续工作。在旁支对话中,**带回** 会把回答放入原对话的输入框。
367
385
 
368
386
  **如何告诉代理在它的差异中要改什么?** 在 **改动面板** 中打开文件的差异,点击某一行的 **+** 并写下批注(**差异批注**,默认开启)。然后点击 **发送到输入框**:批注会连同文件、行号和这些行一起进入输入框。
@@ -371,7 +389,7 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
371
389
 
372
390
  **我的 iPhone 丢了。如何阻止它访问我的 Mac?** 点击侧边栏左下角的 **手机连接**,然后点击该 iPhone 旁边的 **Remove**。它的密钥会立即失效;只有通过你在 Mac 上批准的新配对,它才能重新连接。
373
391
 
374
- **如何在这台 Mac 上继续另一台 Mac 的对话?** 在另一台 Mac 上开启 **设置 → Composer Plus → 远程电脑** 和 **允许其他电脑打开本机**,点击 **创建配对链接** 并复制链接(例如用 AirDrop 或备忘录发给自己)。在这台 Mac 上也开启 **远程电脑**,在对话标题左侧选择 **添加电脑…**,粘贴链接并点击 **连接**,然后在另一台 Mac 上点击 **批准**。之后,在同一个菜单中选择那台电脑即可。
392
+ **如何在这台 Mac 上继续另一台 Mac 的对话?** 在另一台 Mac 上开启 **设置 → Composer Plus → 远程电脑** 和 **允许其他电脑打开本机**,点击 **创建配对链接** 并复制链接(例如用 AirDrop 或备忘录发给自己)。在这台 Mac 上也开启 **远程电脑**,在左侧边栏的选择器(品牌名右侧、**新建会话** 上方)中选择 **添加电脑…**,粘贴链接,输入 **名称** 并点击 **连接**,然后在另一台 Mac 上点击 **批准**。之后,在同一个菜单中选择那台电脑即可:它会占满本窗口。在同一位置选择 **本机** 即可返回;这期间这台 Mac 的对话会继续运行。
375
393
 
376
394
  **可以用 Face ID 锁定 Tether 吗?** 可以:在 Tether 中打开“Settings → Privacy → Lock with Face ID”。之后,Tether 在打开时以及你回到它时,都会要求 Face ID(或密码)。
377
395
 
@@ -403,6 +421,12 @@ English · 中文 · Türkçe · Español · Português (Brasil) · Français ·
403
421
 
404
422
  **dsh 能否不再询问模型就运行下一条命令?** 可以,使用 **计划步骤**(默认关闭):在 设置 → Composer Plus 中开启。之后模型可以带条件地计划接下来的命令,例如“构建通过后运行测试”,结果满足条件时,Composer Plus 会在不重新调用模型的情况下运行该步骤。审批和沙箱保持不变。
405
423
 
424
+ **土耳其语技能来自哪里?可以移除吗?** 来自 GitHub 上的 [yucezerey/turkish-anti-ai-skill](https://github.com/yucezerey/turkish-anti-ai-skill)(MIT)。Composer Plus 只在 **应用语言** 为 Türkçe 时安装它,只保留 Markdown 文件,也不运行其中的脚本。要移除它,在 设置 → Composer Plus 中关闭 **土耳其语技能包**:技能和已下载的副本会立即删除。
425
+
426
+ **如何报告 Composer Plus 的缺陷或请求新功能?** 设置 → Composer Plus → **报告问题…**(或对话的 **…** 菜单 → **报告 Composer Plus 问题…**)。选择类型,用你自己的语言描述,然后点击 **写草稿**。检查英文草稿,然后点击 **创建 issue**。
427
+
428
+ **怎样让智能体整理一个混乱的代码库?** 在对话中输入 `/untangle-codebase`(**理清代码库**,默认开启),可在后面加上要关注的部分,例如 `/untangle-codebase src/api`。智能体会先列出重复代码、隐藏的耦合、多余的包装层、无用代码和特殊处理,然后以目标的方式修复,并用子智能体再次检查。请在 **每个对话一个分支** 的对话中启动,让这些改动留在单独的分支上。
429
+
406
430
  **它支持桌面应用吗?** 支持:设置 → 插件 → **添加插件** → `dsh-composer-plus`。已在 dsh 0.2.0-rc.2(桌面版和网页版)和 0.2.1-rc.2(网页版)上测试。
407
431
 
408
432
  ## 开发
@@ -422,6 +446,8 @@ SHOT=1 PLAYWRIGHT=/path/to/playwright node test/browser-status-order.mjs /path/t
422
446
  SHOT=1 PLAYWRIGHT=/path/to/playwright node test/browser-agentbox-ideas.mjs /path/to/dsh/lib/bin.js
423
447
  # Reject with note, Secret input, Quote in reply, Side chat and Diff notes (fake model + a test-only approval gate): the button on dsh's card, the note after the rejection in the next request, Esc; the secret card, Save, the value in no request, file or page, Decline, Stop; a selected part of an answer → Quote → the sent message; a side question while the chat waits on a card (read-only, no queued message in it), Bring back; notes on a diff line and range → Send to composer → the sent message; switch off + their thumbnails
424
448
  SHOT=1 PLAYWRIGHT=/path/to/playwright node test/browser-t3code-ideas.mjs /path/to/dsh/lib/bin.js
449
+ # Quote from file preview (fake model): lines of a code, text, Markdown and PDF file in the right sidebar → Quote → the path, the line numbers and the lines in the sent message; no button for the path header; switch off + its thumbnail
450
+ SHOT=1 PLAYWRIGHT=/path/to/playwright node test/browser-preview-quote.mjs /path/to/dsh/lib/bin.js
425
451
  # every Settings feature row shows a loading screenshot and a ? link to its README row
426
452
  PLAYWRIGHT=/path/to/playwright node test/browser-settings-shots.mjs /path/to/dsh/lib/bin.js
427
453
  # What's new after an update: the release notes show once (also after an install through another path), Esc and Close close them; Settings → What’s new in this version opens them again
@@ -494,3 +520,5 @@ PLAYWRIGHT=/path/to/playwright node test/browser-language.mjs /path/to/dsh/lib/b
494
520
  ## License
495
521
 
496
522
  MIT
523
+
524
+ `assets/skills/untangle-codebase/SKILL.md` 是 [caldoai/skills](https://github.com/caldoai/skills) 中的 `untangle-codebase` 技能,© 2026 Maze,采用 MIT 许可([其许可证](assets/skills/untangle-codebase/LICENSE))。
Binary file
Binary file
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maze
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: untangle-codebase
3
+ description: Untangle a whole codebase in one pass, so agents working in it read less per change, break less without noticing, and debug faster.
4
+ disable-model-invocation: true
5
+ argument-hint: "[optional area or concern to focus on]"
6
+ ---
7
+
8
+ Start a thorough refactor of this codebase and complete it to the end.
9
+
10
+ Treat this as a goal.
11
+
12
+ **Why do this at all?**
13
+
14
+ - **Better output:** agents write better code in a well-structured codebase.
15
+ - **Cheaper & faster changes:** agents spend fewer tokens searching and more on the problem.
16
+ - **Fewer silent breaks:** agents break less without noticing.
17
+ - **Faster debugging:** agents find root causes faster.
18
+
19
+ **1. Ask**
20
+
21
+ Besides untangling the codebase, there are two other things you may do:
22
+
23
+ | Addition | What it does |
24
+ | --- | --- |
25
+ | **Comments** | Remove comments that only restate the code. |
26
+ | **Tests** | Remove tests that wouldn't catch a real bug, and fix the ones that fail without one. |
27
+
28
+ List these (with their exact descriptions) for the user and ask which ones to do. Then do only those.
29
+
30
+ **2. List every problem**
31
+
32
+ Don't change anything yet.
33
+
34
+ Go through the codebase asking one thing: **should these pieces change together?** Write down every place the code gets the answer wrong, as in the [decision log example](#decision-log-example). There are only three ways to get it wrong.
35
+
36
+ **Hidden links:** pieces that must change together, where the code doesn't show it.
37
+
38
+ - Copies of logic or values: change one and the others must change too, but nothing says so.
39
+ - A module that reads a global or file set by another module: change that module and this one breaks, but nothing shows it (self-contained code).
40
+ - A feature split across folders by layer, or related code in different folders: one change needs all of them, but the layout hides that (domain-driven code, colocation).
41
+ - A call order or convention callers just have to know: change it and callers break, but nothing warns them (explicit over implicit).
42
+
43
+ **Needless links:** pieces tied together that change for different reasons.
44
+
45
+ - Callers that depend on internals: the internals change for their own reasons, and the callers break anyway (encapsulation).
46
+ - Stable code that imports something more volatile: the stable code has to change whenever the volatile code does (dependency direction).
47
+ - A general function with a parameter only one workflow understands: the function has to change whenever that workflow does (caller vocabulary).
48
+ - A shallow module: its interface changes whenever its internals do, so its callers change with it (deep modules).
49
+
50
+ **Surplus:** pieces with no reason of their own to change.
51
+
52
+ - Wrappers: they only change when the thing they wrap does (pass-through wrappers).
53
+ - Extension points and config for things that never vary: nothing will ever change them (speculative generality).
54
+ - Dead code: nothing uses it, so nothing will ever need it to change.
55
+
56
+ Also write down every **special case**: code that exists for one situation instead of following from how the system works. Each one is a requirement the design never accounted for, so use them to work out what the system needs to be.
57
+
58
+ - An `if` for one value
59
+ - A parameter for a specific caller
60
+ - A swallowed exception
61
+ - A retry or sleep
62
+ - A disabled test
63
+
64
+ **3. Redesign from first principles**
65
+
66
+ Work out the fixes as if the system had known today's requirements from the start. Look for the one fix behind several problems, which often looks nothing like them. Don't fix special cases one by one.
67
+
68
+ Ask the same thing of every fix, even one that looks better in the code: pieces that change together end up together, pieces that don't aren't tied, and everything depends on what changes less.
69
+
70
+ Write each fix above the problems it solves, as in the [decision log example](#decision-log-example).
71
+
72
+ **4. Fix everything on the list**
73
+
74
+ Remove the special cases the redesign makes unnecessary, and keep the ones the system still needs. Don't go looking for bugs separately. A structural fix may fix some along the way, which is fine.
75
+
76
+ If the user chose Comments:
77
+
78
+ - Comments that restate the implementation, like `// sort first so the binary search works`, only change when it does, so they have no reason of their own to change. Remove them, even if they're phrased as a why.
79
+ - Comments about something outside the code, like `// the proxy drops websockets, so we poll`, change when that does, and the code can't show it. Keep them.
80
+ - Comments that explain a workaround om the codebase, like `// wait 100ms because the cache isn't ready yet`, only change when the problem behind the workaround does. They go when the redesign removes that problem.
81
+
82
+ If the user chose Tests, do this last, because tests are your regression safety net. A test should fail on a regression, and only then:
83
+
84
+ - Tests that repeat the implementation only change when it does, so they have no reason of their own to change. They check the same interpretation against itself, so remove them.
85
+ - Tests tied to internals break when the code changes but behavior doesn't, so they change for the wrong reason. Rewrite them to depend on behavior, which changes less.
86
+ - Tests that can't fail pass whatever the code does, so no change ever affects them. They aren't checking anything, so remove them.
87
+
88
+ **5. Check, and repeat until done**
89
+
90
+ Have subagents search for special cases, hidden links, needless links and surplus. **Do not stop until there is nothing worth fixing left.**
91
+
92
+ You're done when:
93
+
94
+ - **As few special cases are left in the system as possible.** How many remain is the best measure of how close you are.
95
+ - A typical change lands in one place.
96
+ - Every import is reasonable, and nothing imports anything more volatile than itself.
97
+ - Subagents have no issues left that are worth fixing.
98
+ - Intended behavior is the same as when you started, apart from bugs the refactor fixed. Test this at the end.
99
+
100
+ **Tips**
101
+
102
+ - **Start from a skeleton:** script away the function bodies, so the whole codebase fits in context.
103
+ - **Summarize with subagents:** have them summarize parts of the codebase, so you can reason across all of it at once.
104
+ - **Check the dependency graph often:** script one that shows what imports what, and rerun it as you go.
105
+ - **Enter where leverage is highest:** start looking there first.
106
+ - **Keep a decision log:** compaction is less of a problem that way.
107
+ - **Use Git as evidence:** for how often pieces change, and what changes together.
108
+
109
+ <a id="decision-log-example"></a>**Decision log example**
110
+
111
+ The list of problems:
112
+
113
+ ```md
114
+ - `ui/LogoutDialog.tsx:30` waits 100ms after closing and hopes the message input can take focus by then.
115
+ - `ui/Toolbar.tsx:40` and `ui/Menu.tsx:22` call the same actions, each with its own check for when they're disabled.
116
+ - `checkout/submit.ts:9` depends directly on `payments/stripe.ts`.
117
+ - `shortcuts/keymap.ts:18` has a switch with one case per action, separate from the menu's list.
118
+ - `ui/CommandPalette.tsx:55` keeps its own list of actions, and it's missing the newer ones.
119
+ - `ui/SettingsDialog.tsx:61` sets a global `skipRefocus` flag to stop the logout dialog it opens from sending focus to the message input.
120
+ - `history/undo.ts:30` has a special case for every action that can be undone.
121
+ - `refunds/refund.ts:22` wraps every Stripe call in `if (order.provider === "paypal")`.
122
+ - Every action handler has to call `track()` by hand, and some don't.
123
+ - `ui/Toolbar.test.tsx` and `ui/Menu.test.tsx` test the same actions separately.
124
+ ```
125
+
126
+ Then with the fixes:
127
+
128
+ ```md
129
+ - Make the general `Dialog` give focus back to whatever had it before it opened, so the wait and the flag can both go.
130
+ - `ui/LogoutDialog.tsx:30` waits 100ms after closing and hopes the message input can take focus by then.
131
+ - `ui/SettingsDialog.tsx:61` sets a global `skipRefocus` flag to stop the logout dialog it opens from sending focus to the message input.
132
+ - Define a `PaymentProvider` interface that Stripe and PayPal both implement, so checkout and refunds no longer know which one they're using.
133
+ - `checkout/submit.ts:9` depends directly on `payments/stripe.ts`.
134
+ - `refunds/refund.ts:22` wraps every Stripe call in `if (order.provider === "paypal")`.
135
+ - Make every user action a command with a name, a handler and an undo, so the toolbar, menu, shortcuts, palette and undo history all run from one list.
136
+ - `ui/Toolbar.tsx:40` and `ui/Menu.tsx:22` call the same actions, each with its own check for when they're disabled.
137
+ - `shortcuts/keymap.ts:18` has a switch with one case per action, separate from the menu's list.
138
+ - `ui/CommandPalette.tsx:55` keeps its own list of actions, and it's missing the newer ones.
139
+ - `history/undo.ts:30` has a special case for every action that can be undone.
140
+ - Every action handler has to call `track()` by hand, and some don't.
141
+ - `ui/Toolbar.test.tsx` and `ui/Menu.test.tsx` test the same actions separately.
142
+ ```