@saluzi/saluzi-edu 0.2.44 → 0.2.45

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.
@@ -60,6 +60,10 @@
60
60
  "title": "MOM 混合模型",
61
61
  "path": "docs/guide/mom-mixed-models"
62
62
  },
63
+ {
64
+ "title": "Token 节约模式",
65
+ "path": "docs/guide/token-saving-modes"
66
+ },
63
67
  {
64
68
  "title": "微信控制",
65
69
  "path": "docs/guide/weixin-login"
@@ -140,6 +144,11 @@
140
144
  "name": "stickers",
141
145
  "description": "Order Saluzi stickers",
142
146
  "descriptionZh": "订购 Saluzi 贴纸"
147
+ },
148
+ {
149
+ "name": "update",
150
+ "description": "Update Saluzi CLI to the latest version via npm",
151
+ "descriptionZh": "通过 npm 更新 Saluzi CLI 到最新版本"
143
152
  }
144
153
  ]
145
154
  },
@@ -197,6 +206,11 @@
197
206
  "checkpoint"
198
207
  ]
199
208
  },
209
+ {
210
+ "name": "sa",
211
+ "description": "Toggle SA mode — minimal prompt, 4 core tools, zero background tasks",
212
+ "descriptionZh": "切换 SA 极简模式:4 工具 + 20 行 prompt + 零后台任务"
213
+ },
200
214
  {
201
215
  "name": "skill-search",
202
216
  "description": "Control automatic skill matching during conversations",
@@ -227,6 +241,11 @@
227
241
  "fork"
228
242
  ]
229
243
  },
244
+ {
245
+ "name": "codegraph",
246
+ "description": "CodeGraph management card — view DB status, toggle priority, browse tools",
247
+ "descriptionZh": "CodeGraph 代码知识图谱"
248
+ },
230
249
  {
231
250
  "name": "copy",
232
251
  "description": "Copy Saluzi",
@@ -282,6 +301,15 @@
282
301
  "description": "Manage MCP servers",
283
302
  "descriptionZh": "管理 MCP 服务器"
284
303
  },
304
+ {
305
+ "name": "plugin",
306
+ "description": "Manage Saluzi plugins",
307
+ "descriptionZh": "管理 Saluzi 插件与插件市场",
308
+ "aliases": [
309
+ "plugins",
310
+ "marketplace"
311
+ ]
312
+ },
285
313
  {
286
314
  "name": "pr-comments",
287
315
  "description": "Get comments from a GitHub pull request",
@@ -453,6 +481,11 @@
453
481
  "new"
454
482
  ]
455
483
  },
484
+ "codegraph": {
485
+ "name": "codegraph",
486
+ "description": "CodeGraph management card — view DB status, toggle priority, browse tools",
487
+ "descriptionZh": "CodeGraph 代码知识图谱"
488
+ },
456
489
  "color": {
457
490
  "name": "color",
458
491
  "description": "Set the prompt bar color for this session",
@@ -607,6 +640,15 @@
607
640
  "description": "Enable plan mode or view the current session plan",
608
641
  "descriptionZh": "启用规划模式或查看当前会话计划"
609
642
  },
643
+ "plugin": {
644
+ "name": "plugin",
645
+ "description": "Manage Saluzi plugins",
646
+ "descriptionZh": "管理 Saluzi 插件与插件市场",
647
+ "aliases": [
648
+ "plugins",
649
+ "marketplace"
650
+ ]
651
+ },
610
652
  "poor": {
611
653
  "name": "poor",
612
654
  "description": "Toggle poor mode — disable extract_memories and prompt_suggestion to save tokens",
@@ -651,6 +693,11 @@
651
693
  "checkpoint"
652
694
  ]
653
695
  },
696
+ "sa": {
697
+ "name": "sa",
698
+ "description": "Toggle SA mode — minimal prompt, 4 core tools, zero background tasks",
699
+ "descriptionZh": "切换 SA 极简模式:4 工具 + 20 行 prompt + 零后台任务"
700
+ },
654
701
  "skill-learning": {
655
702
  "name": "skill-learning",
656
703
  "description": "Manage skill learning (observe, analyze, evolve)",
@@ -699,6 +746,11 @@
699
746
  "description": "Change the theme",
700
747
  "descriptionZh": "切换主题"
701
748
  },
749
+ "update": {
750
+ "name": "update",
751
+ "description": "Update Saluzi CLI to the latest version via npm",
752
+ "descriptionZh": "通过 npm 更新 Saluzi CLI 到最新版本"
753
+ },
702
754
  "vim": {
703
755
  "name": "vim",
704
756
  "description": "Toggle between Vim and Normal editing modes",
@@ -3068,6 +3120,25 @@
3068
3120
  },
3069
3121
  "content": "\n## 什么是 /keys\n\n`/keys` 命令管理 Saluzi 的 API Key 绑定。它采用**双栏 TUI 界面**(左栏为模型槽位,右栏为 Key 列表),支持:\n\n- 绑定多个 provider 的 Key(Anthropic、OpenAI、Gemini、Grok、Foundry、自定义等)\n- 将 Key 绑定到不同的**模型槽位**(default / max / pro / std / subagent)\n- 为每个 Key 设置名称、指定 Base URL(transit 代理场景)\n- 编辑、删除、解绑已有配置\n\n## 使用方法\n\n```\n> /keys\n```\n\n打开双栏管理面板。通过键盘快捷键操作:\n\n| 快捷键 | 操作 |\n|--------|------|\n| `a` | 添加新 Key |\n| `e` | 编辑已有 Key(名称/provider/URL/key 值) |\n| `d` | 删除 Key |\n| `b` | 绑定到模型槽位 |\n| `u` | 解绑槽位 |\n| `Esc` | 关闭面板 |\n\n添加 Key 时需要输入:\n- Key 名称(如 `anthropic-work`)\n- Provider(Anthropic Direct / Transit / OpenAI / Gemini / Grok / Foundry / Custom)\n- API Key 值\n- Base URL(Transit、Foundry、Custom 等场景需要)\n\nKey 保存在本地配置中,不会提交到 VCS。\n\n## 模型槽位\n\n`/keys` 的核心概念是**模型槽位**。每个槽位对应一个模型级别:\n\n| 槽位 | 说明 |\n|------|------|\n| `default` | 默认 Key,所有模型共用 |\n| `max` | Max 级别模型专用 Key |\n| `pro` | Pro 级别模型专用 Key |\n| `std` | Std 级别模型专用 Key |\n| `subagent` | 子 agent 专用 Key |\n\n当 `/model max` 执行时,Saluzi 优先使用 `max` 槽位的 Key;未配置时回退到 `default`。\n\n## 环境变量\n\n可通过环境变量预设 Key 配置(CI/CD 场景常用):\n\n```bash\nKEYS_DEFAULT_KEY=sk-xxx slz\nKEYS_MAX_KEY=sk-xxx KEYS_MAX_MODEL=claude-opus-4-7 slz\n```\n\n格式为 `KEYS_{SLOT}_{FIELD}`,其中 SLOT 为 `DEFAULT`/`MAX`/`PRO`/`STD`/`SUBAGENT`,FIELD 为 `KEY`/`PROVIDER`/`MODEL`/`URL`。\n\n## Provider 选择\n\nSaluzi 自动选择最优 Provider。如需指定:\n- 通过环境变量(如 `ANTHROPIC_API_KEY`)指定\n- 通过 `/keys` 绑定特定 provider 的 Key 到对应槽位\n- 通过 `/model` 切换当前会话模型\n\n## 安全建议\n\n- 不要把 Key 写进代码或 commit\n- 用 `/keys` 管理而非环境变量(更安全、可切换)\n- 定期轮换 Key\n"
3070
3122
  },
3123
+ "docs/guide/token-saving-modes": {
3124
+ "frontmatter": {
3125
+ "title": "Token 节约模式 - Poor 与 SA 极简 harness",
3126
+ "description": "Poor 模式关闭后台记忆/提示副作用,SA 模式进一步收敛到 4 工具 + 20 行 prompt 的极简风格 harness。讲清两种模式各砍掉了什么、还剩什么能力、与 MOM/CodeGraph/Memory 等功能如何兼容。",
3127
+ "keywords": [
3128
+ "poor",
3129
+ "SA",
3130
+ "节约模式",
3131
+ "极简",
3132
+ "token",
3133
+ "成本",
3134
+ "权限",
3135
+ "安全",
3136
+ "删库",
3137
+ "省 token"
3138
+ ]
3139
+ },
3140
+ "content": "\n## 为什么需要 Token 节约模式\n\nSaluzi 默认开启大量「让 AI 更聪明」的后台机制:自动记忆提取、提示建议、会话记忆整合、验证 Agent、相关记忆预取、Agent 摘要……这些机制各自只消耗少量 token,但叠在一起会让每次对话的「隐性开销」相当可观。\n\n当你处于以下场景时,这些开销就是纯浪费:\n\n- **API Key 按量计费**,想压到最低\n- **弱模型 / 小上下文窗口**,每一段 prompt 都很贵\n- **一次性脚本任务**,不需要长期记忆\n- **清晰的单文件修改**,不需要多角度验证\n\n两种节约模式就是为这些场景设计的开关,按「砍多少」分档:\n\n| 模式 | 砍掉的范围 | 工具集 | System Prompt | 适合 |\n|------|-----------|--------|--------------|------|\n| **Poor** | 5 项后台副作用 | 全部保留 | 完整 | 日常开发省 token |\n| **SA** | 全部后台 + 工具收敛 | 仅 4 核心 | ~20 行极简 | 极限省 token / 弱模型 |\n\nSA 是 Poor 的**严格超集**——Poor 关掉的一切,SA 也关掉;SA 还额外砍掉工具和 prompt。两者的关系类似「省电模式」与「飞行模式」。\n\n## Poor 节约模式\n\n### 是什么\n\n`/poor` 切换 Poor 模式。开启后 Saluzi 跳过 5 类后台副作用任务,并对若干辅助模型调用降级。**主对话的工具集与 system prompt 完全不变**——你只是少了一些「后台 whisper」。\n\n### 关闭了什么\n\n| 被关闭的子系统 | 位置 | 原本做什么 |\n|---------------|------|-----------|\n| extract_memories | Stop hook | 每轮结束后从对话提取记忆写入 MEMORY.md |\n| prompt_suggestion | Stop hook | 给用户猜测「下一步可能想问什么」 |\n| autoDream | Stop hook | 后台整合/蒸馏历史记忆 |\n| AgentSummary | 后台 | 给子 Agent 生成摘要 |\n| 验证 Agent | System prompt | 非平凡改动后强制独立审查 |\n\n同时这些**辅助模型调用降级**(不影响主模型):\n\n| 调用点 | 原模型 | Poor 后 |\n|--------|--------|---------|\n| autoMode critique | MainLoop | SmallFast |\n| permission explainer | MainLoop | SmallFast |\n| universal classifier | MainLoop | DefaultPro |\n| yolo classifier | MainLoop | DefaultPro |\n\n降级意味着权限分类、自动模式批评等「辅助判断」用更便宜的模型跑,质量略降但成本显著下降。主对话模型不变。\n\n### 优势\n\n- **工具集完整**:Read/Edit/Write/Bash/Glob/Grep/WebFetch/Task/MCP……全部照常\n- **System prompt 完整**:项目记忆、CodeGraph、技能、SALUZI.md 仍然注入\n- **可逆且持久**:`/poor` 再按一次关闭;状态写入 `settings.json` 的 `poorMode` 字段,重启后保留\n- **粒度温和**:只砍「后台 whisper」,主循环质量基本无损\n\n### 劣势\n\n- **不再自动积累记忆**:MEMORY.md 不会自动更新,需要手动 `/memory` 编辑\n- **无提示建议**:终端不再显示「你可能想问……」的快捷建议\n- **无验证 Agent**:复杂改动后没有独立审查兜底,需要自己跑测试\n- **辅助分类降级**:权限判断、自动模式批评的精度略降\n\n## SA 极简模式\n\n### 是什么\n\n`/sa` 切换 SA 模式(极简风格 minimal harness)。开启后 Saluzi 收敛到「最小可用 harness」:\n\n- **System prompt 收缩到 ~20 行**:只有角色定义 + 4 个工具说明 + 基本准则 + cwd + 日期。没有动态段落、没有项目记忆、没有 CodeGraph、没有 MCP 说明、没有会话指导。\n- **工具收敛到 4 个核心**:Read / Edit / Write / Bash。其余工具(Glob、Grep、WebFetch、Task、TodoWrite、MCP、Skill……)全部移除。\n- **所有后台副作用归零**:Poor 关掉的 5 项 + 会话记忆 + 相关记忆预取,全部跳过。\n\n这是「把 Saluzi 当成一个极简的 Read/Edit/Write/Bash 编码助手」的开关——类似极简 harness 的体验。\n\n### SA 的 System Prompt 长什么样\n\n完整内容就这些:\n\n```\nYou are an expert coding assistant operating inside Saluzi, a coding agent harness.\nYou help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n- Read: Read file contents from the filesystem\n- Edit: Make targeted edits to existing files\n- Write: Create new files or overwrite existing files\n- Bash: Execute shell commands\n\nGuidelines:\n- Be concise in your responses\n- Show file paths clearly when working with files\n- Use file_path:line_number format when referencing code locations\n- Do not use a colon before tool calls\n- Ask the user before making significant architectural decisions\n\nCurrent working directory: <cwd>\nDate: <date>\n```\n\n对比默认 prompt 的数千字(项目记忆 + CodeGraph + 技能 + 会话指导 + 工具说明 + 安全规则……),SA prompt 几乎是零开销。\n\n### 关闭了什么\n\nSA 关闭 = Poor 的全部 + 以下额外项:\n\n| 额外关闭项 | 位置 | 原本做什么 |\n|-----------|------|-----------|\n| 会话记忆提取 | sessionMemory | 跨会话蒸馏项目级记忆 |\n| 相关记忆预取 | attachments | 每轮用户消息后 side-query 检索相关记忆 |\n| 全部工具(除 4 核心) | tools | Glob/Grep/Task/WebFetch/MCP/Skill 等 |\n| 完整 system prompt | prompts | 替换为 20 行极简版 |\n\n### 优势\n\n- **极限省 token**:prompt 从数千字降到 ~20 行,每轮省下大量输入 token\n- **弱模型友好**:小上下文窗口 / 弱模型不会被巨型 prompt 挤占空间\n- **响应快**:没有后台 side-query 拖慢首字节\n- **行为可预测**:AI 只有 4 个工具,不会偷偷跑 WebFetch / Task / MCP,调试简单\n- **可逆且持久**:`/sa` 再按一次关闭;状态写入 `settings.json` 的 `saMode` 字段\n\n### 劣势\n\n- **没有 Glob/Grep**:AI 找文件只能用 `Bash(ls/find)` + `Read`,搜索效率低\n- **没有 Task/TodoWrite**:无法拆分多步任务、不能 spawn 子 Agent\n- **没有 WebFetch/WebSearch**:不能查文档、不能上网\n- **没有 MCP/Skill**:所有 MCP 服务器工具、自定义技能全部不可用\n- **没有项目记忆**:SALUZI.md、MEMORY.md、CodeGraph 知识都不注入,AI 对项目「无记忆」\n- **没有验证 Agent**:同 Poor\n- **AI 不知道自己的完整能力**:极简 prompt 不说明权限模式、安全规则、提交规范等,复杂工作流需要你手动提示\n\n> 简言之:SA 模式适合「我就让 AI 改这一个文件」「跑个脚本看看」这类窄任务,不适合复杂架构重构或需要项目上下文的工作。\n\n## 两种模式的完整对比\n\n| 维度 | 默认 | Poor | SA |\n|------|------|------|-----|\n| System prompt | 数千字(完整动态) | 数千字(完整动态) | ~20 行(极简) |\n| 工具集 | 全部 | 全部 | 4 核心(Read/Edit/Write/Bash) |\n| extract_memories | ✅ | ❌ | ❌ |\n| prompt_suggestion | ✅ | ❌ | ❌ |\n| autoDream | ✅ | ❌ | ❌ |\n| AgentSummary | ✅ | ❌ | ❌ |\n| 验证 Agent | ✅ | ❌ | ❌ |\n| 会话记忆提取 | ✅ | ✅ | ❌ |\n| 相关记忆预取 | ✅ | ✅ | ❌ |\n| 项目记忆注入 (SALUZI.md) | ✅ | ✅ | ❌ |\n| CodeGraph | ✅ | ✅ | ❌ |\n| MCP 工具 | ✅ | ✅ | ❌ |\n| Skill 工具 | ✅ | ✅ | ❌ |\n| Glob/Grep/WebFetch/Task | ✅ | ✅ | ❌ |\n| 辅助分类模型 | MainLoop | Pro/SmallFast | Pro/SmallFast |\n| 主对话模型 | 不变 | 不变 | 不变 |\n| 输入 token / 轮 | 基准 | 略低(少后台 side-query) | 显著低(prompt 极小) |\n| 适合场景 | 日常全功能 | 日常省 token | 极限省 / 弱模型 / 窄任务 |\n\n## 与其他功能的兼容性\n\n这是最关键的部分——开了节约模式后,你常用的那些 Saluzi 特色还能不能用。\n\n### MOM 混合模型\n\n| 模式 | 兼容性 | 说明 |\n|------|--------|------|\n| Poor | ✅ 完全兼容 | Poor 不触碰 MOM 配置,顾问与主机按 `/mom` 设置正常运行 |\n| SA | ⚠️ 受限兼容 | MOM 仍可运行,但**主机只有 4 个工具**(Read/Edit/Write/Bash);顾问本就是 text-only 不受影响 |\n\n> 如果在 SA 模式下用 mom-stair,弱模型顾问(text-only)正常工作;升级到主机后主机也只能用 4 工具执行——不能 Glob/Grep/WebFetch。建议 SA + MOM 时把主机任务限定在文件读写 + 命令执行。\n\n### CodeGraph 代码智能\n\n| 模式 | 兼容性 | 说明 |\n|------|--------|------|\n| Poor | ✅ 完全兼容 | CodeGraph 工具与 prompt 段落照常注入 |\n| SA | ❌ 不可用 | 工具被收敛到 4 核心(无 CodeGraph 工具),prompt 也不含 CodeGraph 段落 |\n\n### 记忆系统 (Memory V2 / SALUZI.md)\n\n| 模式 | 兼容性 | 说明 |\n|------|--------|------|\n| Poor | ⚠️ 只读不写 | 已有记忆仍注入 prompt;但 extract_memories/autoDream 关闭,不再自动积累新记忆 |\n| SA | ❌ 完全隔离 | 既不注入(极简 prompt 无记忆段),也不提取(session memory 关闭) |\n\n### 验证 Agent (Verification Agent)\n\n| 模式 | 兼容性 |\n|------|--------|\n| Poor | ❌ 跳过 |\n| SA | ❌ 跳过 |\n\n两种模式都跳过验证 Agent。需要独立审查时请关闭节约模式后再跑,或手动 spawn 子 Agent(SA 模式下无 Task 工具,需先关 SA)。\n\n### Plan 模式 / Sandbox / Hooks\n\n| 模式 | Plan | Sandbox | Hooks |\n|------|------|---------|-------|\n| Poor | ✅ | ✅ | ✅ |\n| SA | ✅ | ✅ | ✅ |\n\n权限模式、沙箱、hooks 都是工具执行层机制,与节约模式正交,完全不受影响。\n\n### MCP 服务器 / Skill\n\n| 模式 | MCP | Skill |\n|------|-----|-------|\n| Poor | ✅ | ✅ |\n| SA | ❌ 工具被移除 | ❌ 工具被移除 |\n\nSA 模式下 MCP 服务器仍可连接(连接层不变),但 MCP 提供的工具不在 4 核心之列,不会被注入工具列表。\n\n### Auto Mode (/auto)\n\n| 模式 | 兼容性 | 说明 |\n|------|--------|------|\n| Poor | ⚠️ 降级 | autoMode 仍运行,但 critique 步骤用 SmallFast 模型 |\n| SA | ⚠️ 降级 | 同 Poor;且主机工具受限 |\n\n### Buddy 伴侣 / Proactive 主动助手\n\n| 模式 | 兼容性 |\n|------|--------|\n| Poor | ✅ Buddy/Proactive 不在 Poor 的关闭清单内 |\n| SA | ⚠️ SA 的设计哲学是「零后台副作用」,建议关闭 Buddy/Proactive 以获得最纯净体验 |\n\n## 权限模式与安全风险\n\n> 用户最关心的问题:**开了节约模式,AI 会不会偷偷执行 `rm -rf /`、`drop database` 这类删库命令?**\n>\n> 简短回答:**节约模式本身不改变权限策略,不会让危险命令「更容易跑」**。真正的风险来自你选的**权限模式**(`default` / `acceptEdits` / `bypassPermissions` / `auto`),而不是 Poor/SA。但 Poor/SA 会把权限分类器降级到更便宜的模型,在 `bypassPermissions`/`auto` 模式下**理论上**误判概率略升。下面逐层拆解。\n\n### 权限模式与节约模式正交\n\n权限模式由 `/permissions` 或 `settings.json` 的 `permissions.defaultMode` 控制,**与 Poor/SA 完全独立**。开 Poor 或 SA 不会切换你的权限模式:\n\n| 权限模式 | Bash 危险命令会怎样 | Poor/SA 影响 |\n|---------|---------------------|-------------|\n| `default` | 每条命令弹确认,`rm -rf` 类必问你 | 无——该问还是问 |\n| `acceptEdits` | 只自动放行文件编辑,**Bash 仍逐条问** | 无——Bash 仍问 |\n| `plan` | 只读,不能执行任何写操作 | 无——根本跑不了 |\n| `bypassPermissions` | 分类器自动判断,不问你 | ⚠️ 分类器降级(见下) |\n| `auto` | autoMode 分类器自动判断 | ⚠️ 分类器降级(见下) |\n\n**结论**:在 `default` / `acceptEdits` / `plan` 这三种模式下,无论开不开 Poor/SA,危险命令都必须经过你手动确认——**不存在「开了 SA 就自动删库」的情况**。\n\n### SA 模式仍执行 deny 规则\n\nSA 模式收敛工具集时调用的是 `filterToolsByDenyRules`——也就是说你在 `settings.json` 里配的 `permissions.deny` 规则**在 SA 模式下照常生效**:\n\n```json\n{\n \"permissions\": {\n \"deny\": [\n \"Bash(rm -rf:*)\",\n \"Bash(rm -rf /*:*)\",\n \"Bash(drop database:*)\",\n \"Bash(git push --force:*)\",\n \"Bash(:(){ :|:& };:)\",\n \"Bash(mkfs*:*)\"\n ]\n }\n}\n```\n\n这些规则会在工具列表阶段就**直接屏蔽**匹配的命令——AI 根本看不到这些工具能跑这类命令,分类器也不会被调用。这是比分类器更硬的防线,且**不受 Poor/SA 影响**。\n\n> 强烈建议:无论用不用节约模式,都把 `rm -rf /`、`drop database`、`mkfs`、fork 炸弹等不可逆命令写进 `deny`。这是「物理隔离」级别的一刀切。\n\n### 分类器降级:风险有多大\n\n在 `bypassPermissions` 或 `auto` 模式下,命令是否自动放行由**分类器**(yoloClassifier / universalClassifier)决定。Saluzi 的分类器 prompt 明确把以下列为 **Irreversible Local Destruction(不可逆本地破坏)**:\n\n- `rm -rf` 递归强制删除非平凡路径\n- `Remove-Item -Recurse -Force`(PowerShell 等价物)\n- `> file` 截断已有文件为空\n- `drop database` 删库\n- `git push --force`(视上下文)\n\nPoor/SA 模式下分类器模型降级:\n\n| 模式 | 分类器模型 | 影响 |\n|------|-----------|------|\n| 默认 | MainLoop(如 Max/Pro 主模型) | 精度最高 |\n| Poor/SA | DefaultPro | 略低,但仍是 Pro 级模型 |\n\n**关键点**:\n\n- 分类器**仍然运行**——Poor/SA 没有关闭分类器,只是让它用更便宜的模型跑。\n- Pro 级模型对 `rm -rf /` 这种明显的破坏性命令识别能力依然很强——这不是「盲放」。\n- 降级影响的是**边界模糊**的命令(如 `rm -rf ./build` 这种有歧义的)的判断精度,不是放行 `rm -rf /`。\n\n**所以「删库」风险的真实情况**:\n\n1. `default` / `acceptEdits` / `plan` 模式:**零风险**,必须你确认。\n2. `bypassPermissions` / `auto` + 默认:**低风险**,分类器(MainLoop)会拦。\n3. `bypassPermissions` / `auto` + Poor/SA:**略升的边界风险**,分类器(Pro)对明显破坏命令仍拦,但对模糊命令误判概率略高。\n4. 任何模式 + 配了 `deny` 规则:**零风险**,物理屏蔽。\n\n### SA 模式的特殊风险面\n\nSA 模式只保留 4 个工具(Read/Edit/Write/Bash),这反而**缩小**了攻击面:\n\n- **没有 WebFetch/WebSearch**:AI 不能下载并执行远程脚本。\n- **没有 Task**:不能 spawn 子 Agent 绕过主循环审查。\n- **没有 MCP 工具**:外部 MCP 服务器不能注入未审查的工具。\n- **没有 Skill**:自定义技能不会被执行。\n\n但 **Bash 仍在**——这是 SA 模式下唯一的「能干坏事」的入口。只要 Bash 的权限策略到位(`default` 模式或 `deny` 规则),SA 模式的整体风险面比默认模式**更小**,不是更大。\n\n### 安全使用建议\n\n| 建议 | 原因 |\n|------|------|\n| 默认用 `default` 或 `acceptEdits` 权限模式 | Bash 逐条确认,物理安全 |\n| 在 `settings.json` 配 `permissions.deny` 屏蔽 `rm -rf /`、`drop database`、`mkfs`、fork 炸弹 | 一刀切物理隔离,不受任何模式影响 |\n| 开启沙箱(`sandbox.enabled: true`)| 限制写入路径与网络访问,`rm -rf /` 直接失败 |\n| 避免 `bypassPermissions` + Poor/SA 组合 | 分类器降级 + 不问你 = 信任分类器,边界命令风险略升 |\n| SA 模式下尤其盯紧 Bash 输出 | 没有验证 Agent 兜底,需要你自己看命令 |\n| 用 `/permissions` 临时切 `plan` 模式做危险探索 | 只读,根本不能执行写操作 |\n\n### 一句话总结\n\n**Poor/SA 不会让 Saluzi「更敢删库」**——它们只砍后台副作用和工具数量,不动权限策略与 deny 规则。真正的删库风险来自 `bypassPermissions`/`auto` 权限模式本身;要彻底消除风险,配 `deny` 规则 + 开沙箱 + 用 `default` 模式,这三道防线与节约模式正交,同时开启完全安全。\n\n## 如何开启 / 关闭\n\n### 命令切换\n\n```\n> /poor # 切换 Poor 模式(开↔关)\n> /sa # 切换 SA 模式(开↔关)\n```\n\n输出会确认当前状态,例如:\n\n```\nSA mode ON — minimal prompt, 4 tools (Read/Edit/Write/Bash), all background tasks disabled\n```\n\n### 配置面板\n\n`/config` → 找到 `SA mode (minimal harness)` 与 `poor mode` 开关,`Enter` 切换。\n\n### settings.json\n\n直接编辑 `~/.saluzi-edu/settings.json`:\n\n```json\n{\n \"poorMode\": true,\n \"saMode\": true\n}\n```\n\n两个字段都可选,`true` 开启、省略或 `false` 关闭。重启 Saluzi 后生效。\n\n> **不要同时开 Poor 和 SA**。SA 是 Poor 的严格超集,同时开启只是冗余——工具集与 prompt 都按 SA 走,Poor 的标志位不起额外作用。需要极限省 token 就直接开 SA;需要保留全部工具就开 Poor。\n\n## 何时该用哪个\n\n| 你的情况 | 推荐 |\n|---------|------|\n| 日常开发,想省点 token,但还要用 Glob/Grep/MOM/CodeGraph | **Poor** |\n| API Key 按量计费,想压到最低 | **Poor**(日常)或 **SA**(窄任务) |\n| 用弱模型 / 小上下文窗口 | **SA** |\n| 一次性脚本:改一个文件、跑个命令 | **SA** |\n| 复杂架构重构、需要项目记忆与多步任务 | **关闭**(用默认) |\n| 需要 MOM 多顾问交叉验证 | **Poor**(保留工具)或 **关闭** |\n| 调试时想要「AI 不要偷偷干别的」 | **SA** |\n| 长期挂着的会话,不想后台烧 token | **Poor** |\n\n## 节约效果怎么看\n\n开启后用 `/cost` 观察单轮 token:\n\n```\n> /cost\n```\n\n重点看**输入 token**——SA 模式下每轮输入 token 会显著下降(因为 prompt 从数千字缩到 ~20 行)。后台 side-query 的输出 token 也归零。多轮对比即可看到差异。\n\n## 下一步\n\n- [MOM 混合模型](./mom-mixed-models) — 节约模式下 MOM 如何运行\n- [模型选择与切换](./model-selection) — `/poor` 命令的简述与模型降级\n- [查看消耗](./cost-usage) — `/cost` 监控节约效果\n- [记忆系统](./memory-system) — Poor/SA 对记忆的影响\n"
3141
+ },
3071
3142
  "docs/guide/assistant-proactive": {
3072
3143
  "frontmatter": {
3073
3144
  "title": "Kairos 与自动助手",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saluzi/saluzi-edu",
3
- "version": "0.2.44",
3
+ "version": "0.2.45",
4
4
  "description": "Saluzi CLI - interactive AI coding assistant in the terminal",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,6 +29,7 @@
29
29
  "files": [
30
30
  "dist",
31
31
  "scripts/acp-link.mjs",
32
+ "scripts/install-defaults.mjs",
32
33
  "scripts/run-parallel.mjs",
33
34
  "packages/remote-control-server/src",
34
35
  "packages/remote-control-server/web/dist",
@@ -43,6 +44,7 @@
43
44
  "dev:inspect": "bun run scripts/dev-debug.ts",
44
45
  "parade": "bun run scripts/dev.ts parade",
45
46
  "prepublishOnly": "bun run build:vite",
47
+ "postinstall": "node scripts/install-defaults.mjs",
46
48
  "lint": "biome lint .",
47
49
  "lint:fix": "biome lint --fix .",
48
50
  "format": "biome format --write .",
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ // Postinstall: merges baseline dangerous-command deny rules into the user's
3
+ // global ~/.saluzi-edu/settings.json so every fresh `npm install` / update
4
+ // ships with irreversible-command protection by default.
5
+ //
6
+ // Hard constraints (do not regress):
7
+ // - Runs in the install env: Node only (>=20), no bun, no transpile, no deps.
8
+ // Only `dist` + whitelisted scripts are published — do NOT import from src.
9
+ // - Plain JS (.mjs): no TypeScript annotations — Node parses this verbatim.
10
+ // - Must NEVER throw: a postinstall failure breaks the whole install. Every
11
+ // code path funnels into a clean return.
12
+ // - Idempotent: dedup by trimmed case-insensitive comparison; safe to run on
13
+ // every update without bloating the array or rewriting an already-conforming
14
+ // file (preserves mtime + user formatting).
15
+ // - Merge-only: preserves all existing user settings; appends solely the
16
+ // baseline rules that are absent. Never overwrites or reorders existing rules.
17
+ // - Respects SALUZI_CONFIG_DIR env override (mirrors runtime
18
+ // getSaluziConfigHomeDir) and ~ expansion, so the install-time path matches
19
+ // the runtime path exactly (avoids the "~.saluzi-edu" literal-tilde bug).
20
+ // - Cross-platform via node:os.homedir() + node:path.join.
21
+ //
22
+ // Coverage caveat: `npm/yarn/pnpm/bun install --ignore-scripts` skips this.
23
+ // A runtime seed fallback would be needed for full coverage; this script is
24
+ // the install-time vector only, per the explicit feature request.
25
+ import { homedir } from 'node:os'
26
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
27
+ import { dirname, join } from 'node:path'
28
+
29
+ /**
30
+ * Expand a leading `~` to the user's home dir. Inlined (rather than imported
31
+ * from src) so the script has zero non-`node:` deps and works in the published
32
+ * package where `src` is not shipped. Mirrors runtime expandTilde behavior.
33
+ */
34
+ function expandTilde(p) {
35
+ return p.startsWith('~') ? join(homedir(), p.slice(1)) : p
36
+ }
37
+
38
+ /**
39
+ * Baseline deny rules — irreversible / destructive commands blocked by
40
+ * default for every user. Keep in sync with the safety policy.
41
+ *
42
+ * Escaping notes:
43
+ * - Fork-bomb `:(){ :|:& };:` contains `()`. The permission-rule parser
44
+ * rejects unescaped empty parens ("Empty parentheses"), so the parens in
45
+ * the rule content must be escaped as `\(` / `\)`. In JS source each `\`
46
+ * is `\\`; JSON.stringify doubles them back to `\\` in the file, which
47
+ * round-trips to a single `\` when parsed. Verified end-to-end.
48
+ * - `mkfs*` uses wildcard (not legacy `:*`) so it matches `mkfs`,
49
+ * `mkfs.ext4`, `mkfs.vfat`, etc. `mkfs*:*` would treat `*` literally and
50
+ * never match.
51
+ */
52
+ export const BASELINE_DENY_RULES = [
53
+ 'Bash(rm -rf:*)',
54
+ 'Bash(rm -rf /*:*)',
55
+ 'Bash(drop database:*)',
56
+ 'Bash(git push --force:*)',
57
+ 'Bash(:\\(\\){ :|:& };:)',
58
+ 'Bash(mkfs*)',
59
+ ]
60
+
61
+ /**
62
+ * Merge baseline deny rules into an existing deny array, preserving order
63
+ * and user-defined rules. Dedup is trimmed + case-insensitive so cosmetic
64
+ * differences (trailing spaces, case) don't leave duplicates, but the
65
+ * user's original spelling is kept when a match already exists.
66
+ *
67
+ * Pure + dependency-free so it can be unit-tested directly.
68
+ *
69
+ * @param {unknown} existingDeny - Current permissions.deny contents (may be
70
+ * undefined or contain non-strings; those are filtered out to mirror the
71
+ * runtime filterInvalidPermissionRules behavior).
72
+ * @param {string[]} [baselineDeny] - Baseline rules to ensure are present.
73
+ * @returns {string[]} New deny array with missing baseline rules appended in order.
74
+ */
75
+ export function mergeDenyRules(
76
+ existingDeny,
77
+ baselineDeny = BASELINE_DENY_RULES,
78
+ ) {
79
+ const existing = Array.isArray(existingDeny)
80
+ ? existingDeny.filter(r => typeof r === 'string')
81
+ : []
82
+
83
+ const present = new Set(existing.map(r => r.trim().toLowerCase()))
84
+ const toAdd = baselineDeny.filter(r => !present.has(r.trim().toLowerCase()))
85
+
86
+ return [...existing, ...toAdd]
87
+ }
88
+
89
+ /**
90
+ * Resolve the global settings.json path, mirroring runtime
91
+ * getSaluziConfigHomeDir: honor SALUZI_CONFIG_DIR, expand a leading `~`,
92
+ * then append settings.json. Exported for testability.
93
+ * @returns {string}
94
+ */
95
+ export function resolveSettingsPath() {
96
+ const raw = process.env.SALUZI_CONFIG_DIR ?? join(homedir(), '.saluzi-edu')
97
+ return join(expandTilde(raw), 'settings.json')
98
+ }
99
+
100
+ /**
101
+ * Apply baseline deny rules to a parsed settings object, returning a new
102
+ * object with missing rules appended. Pure: does not mutate the input or
103
+ * touch the filesystem.
104
+ *
105
+ * Returns null when the existing file is structurally unparseable in a way
106
+ * the runtime parser would also reject (non-object top level), signalling
107
+ * the caller to skip writing rather than clobber the user's file.
108
+ *
109
+ * Returns the input unchanged (by value) when all baseline rules are already
110
+ * present, so the caller can detect "no change" via JSON equality and skip
111
+ * the filesystem write — preserving mtime and the user's exact formatting.
112
+ *
113
+ * @param {unknown} settings
114
+ * @param {string[]} [baselineDeny]
115
+ * @returns {Record<string, unknown> | null}
116
+ */
117
+ export function applyBaselineToSettings(
118
+ settings,
119
+ baselineDeny = BASELINE_DENY_RULES,
120
+ ) {
121
+ if (settings === null || settings === undefined) {
122
+ return { permissions: { deny: mergeDenyRules([], baselineDeny) } }
123
+ }
124
+ if (typeof settings !== 'object' || Array.isArray(settings)) {
125
+ // Non-object top level — runtime parser would reject this. Don't clobber.
126
+ return null
127
+ }
128
+
129
+ const base = settings
130
+ const result = { ...base }
131
+
132
+ const hasPerms =
133
+ base.permissions !== null &&
134
+ typeof base.permissions === 'object' &&
135
+ !Array.isArray(base.permissions)
136
+ const perms = hasPerms ? { ...base.permissions } : {}
137
+
138
+ const existingDeny = Array.isArray(perms.deny)
139
+ ? perms.deny.filter(r => typeof r === 'string')
140
+ : []
141
+ const merged = mergeDenyRules(existingDeny, baselineDeny)
142
+
143
+ if (merged.length !== existingDeny.length) {
144
+ perms.deny = merged
145
+ result.permissions = perms
146
+ }
147
+ return result
148
+ }
149
+
150
+ function main() {
151
+ try {
152
+ const settingsPath = resolveSettingsPath()
153
+ mkdirSync(dirname(settingsPath), { recursive: true })
154
+
155
+ let existing = {}
156
+ if (existsSync(settingsPath)) {
157
+ const raw = readFileSync(settingsPath, 'utf8')
158
+ if (raw.trim() !== '') {
159
+ try {
160
+ existing = JSON.parse(raw)
161
+ } catch {
162
+ // User's settings.json has a JSON syntax error. Do NOT overwrite —
163
+ // the runtime parser surfaces this to the user via /status. Bail
164
+ // out cleanly so the install still succeeds.
165
+ return
166
+ }
167
+ }
168
+ }
169
+
170
+ const next = applyBaselineToSettings(existing)
171
+ if (next === null) {
172
+ // Non-object top level — can't safely merge. Leave the file alone.
173
+ return
174
+ }
175
+
176
+ // Skip the write when nothing changed — preserves mtime + user formatting.
177
+ if (JSON.stringify(next) === JSON.stringify(existing)) {
178
+ return
179
+ }
180
+
181
+ writeFileSync(settingsPath, JSON.stringify(next, null, 2) + '\n', 'utf8')
182
+ } catch {
183
+ // Permissions, disk full, EACCES, … — never break the install.
184
+ }
185
+ }
186
+
187
+ main()