高级治理阅读约 14 分钟最后核对 2026-09-20

权限与治理

Spark Work 权限治理:会话权限模式与 Profile 规则的两层判定、审批卡四档与超时行为、Rules 作用域、经典 Hooks 与 Hooks V2、用量账本,以及当前真正可查的审计记录入口。

Spark Work 的「权限与治理」不是一个开关,而是两条独立防线加几条旁路记录:会话权限模式决定 SDK 层是否把工具调用送到审批(apps/desktop/src/renderer/design/utils/permission-options.ts);权限 Profile 规则决定到达审批时是自动放行、自动拦截还是弹卡 (packages/agent-runtime/src/services/permission.service.ts)。 规则(Rules)、Hooks、用量账本、本地日志是另外四块独立能力,存储与入口各不相同。 本文只写代码里真实存在的枚举、入口与默认值,并明确标注哪些旧文档提法在当前代码中不存在。

1. 第一层:会话权限模式(SDK 层)

会话权限模式可按执行器(adapter)选择,三种适配器的可选值互斥。真实枚举与界面文案来自permission-options.ts,类型定义在packages/protocol的 SessionPermissionMode。

执行器模式键名界面文案界面说明
claude-sdk/claudeclaude-ask请求批准每次工具执行前确认
claude-sdk/claudeclaude-plan计划模式先产出计划,再批准执行
claude-sdk/claudeclaude-auto-edits自动编辑自动批准文件编辑
claude-sdk/claudeclaude-auto自动审批使用自动权限策略
claude-sdk/claudeclaude-bypass完全访问完全由 agent 执行
codexcodex-default按需批准workspace-write;工作区内安全写入自动执行,越界操作请求批准
codexcodex-auto-review替我批准workspace-write;越界操作交由 Codex 自动审查
codexcodex-full-access完全访问danger-full-access;允许修改.git和工作区外文件
sparkspark-default手动审批只读工具直接执行;写入与命令逐次确认
sparkspark-auto自动审批所有工具自动执行(显式 deny 规则仍生效)
sparkspark-bypass完全访问跳过全部审批与规则,完全由 agent 执行

协议里还保留了旧值spark-accept-edits、spark-plan(只为已存储会话兼容, 界面不再提供)。模式不是「每次都要审批」的同义词,它的实际作用是映射到各内核的原生策略:

  • Claude:claude-ask→ SDKdefault;claude-auto-edits→ acceptEdits;claude-auto→auto;claude-bypass→bypassPermissions;claude-plan→ plan(只读 enforcement 交给 SDK 原生 plan 模式,不再把 Write/Edit 放进 disallowedTools)。
  • Claude 路径还有一组永远拒绝的模式:Bash(rm -rf /:*)、 fork bomb、Bash(mkfs:*)、Bash(dd if=/dev/zero:*),以及预设自带的 Skill工具(Spark Work 用自己的 Skill 体系,调用预设 Skill 只会报 Unknown skill)。
  • Codex:codex-full-access→sandboxMode: danger-full-access+ approvalPolicy: never;codex-auto-review→ workspace-write+approvalPolicy: on-request+ approvalsReviewer: auto_review;codex-default→ workspace-write+on-request(无人值守场景降为never)。

三个真实入口:

  • 输入框权限下拉(会话内切换):主对话框与 Agent 配置复用同一份 permission-options.ts,切换后会热更新运行中的 Claude 执行器。
  • 设置 → 权限策略 → SDK 执行默认策略:可选「默认执行器」(Claude SDK / Codex / Spark)与 「默认权限策略」。这份默认值落在app_settings的 runtime-permissions/defaults,只作用于新会话与无会话输入区; 已有会话保留自己的策略。默认值是claude-sdk+claude-ask(Codex 适配器回落codex-default,Spark 回落spark-default)。
  • 协议侧session:update也带permissionMode,可对已有会话改模式。

2. 第二层:权限 Profile 规则与审批卡

规则层在设置 → 权限策略(左栏分组「Agent」下的「权限策略」)。它由两部分组成: 选中的Profile,以及该 Profile 下的动作类目规则。

动作类目是固定的 7 项,界面名称与说明如下(真实取值就是这些 action 字符串):

action界面名称覆盖的工具
file_read读取文件Read / Glob / Grep 等读取类工具
file_write编辑文件Write / Edit 等写入类工具
command_exec执行 shell 命令非破坏性命令
command_dangerous高风险命令rm -rf、git reset --hard、sudo等
network_known搜索网络WebSearch 等已知安全入口
network_unknown访问任意网页WebFetch 抓取任意 URL
mcp_tool调用 MCP 工具所有mcp__前缀的第三方工具

每条规则有四种模式(下拉里是中文):allow允许、ask询问、ask-twice双重确认、deny拒绝。三个内置 Profile 的语义与默认规则种子:

  • strict(一切都问):file_read/file_write/command_exec/network_known/network_unknown/mcp_tool均为ask,command_dangerous为 ask-twice。
  • project-standard(工作区写入自动允许,默认 Profile):file_read/file_write/network_known/mcp_tool为allow,command_exec为ask,command_dangerous为ask-twice,network_unknown为 ask。
  • trusted(自动允许大多数):除command_dangerous为 ask外, 其余动作类目都是allow。

存储位置:permission_profiles/permission_rules/ permission_settings三张表,当前激活的 Profile 存在 permission_settings的permission:active-profile。 IPC 里还有 permission:create-profile/permission:delete-profile, 但设置页目前只用到「列出 Profile / 改规则 / 切换激活 Profile」,没有新建/删除 Profile 的界面入口。

一次审批的完整判定顺序(PermissionService.requestApproval):

  1. 先把工具名映射成动作类目:mcp__/mcp:前缀 →mcp_tool;Bash/bash/run_command/git等 → command_exec, 若命令命中危险正则则升级为command_dangerous;无法映射的工具兜底为command_exec,并且拿不到规则时模式默认ask(更安全)。
  2. 危险命令识别用的是正则,不是关键字列表:rm -rf、git clean -fx、git reset --hard、sudo、chmod -r、chown -r、dd if=、mkfs、经典 fork bomb。
  3. 查会话级内存决策(用户点过「会话允许 / 会话拒绝」)→ 查 Profile 规则 → 查 permission_decisions表里的项目/全局记忆(键是action+ toolName)。allow直接放行、deny直接拦截。
  4. ask/ask-twice才推审批卡;ask-twice会连弹两次, 两次都放行才通过,任一次拒绝即拒绝。

审批卡长什么样(渲染端真实文案):

  • 标题:允许执行 <toolName>?;副标题显示 Session xxxxxxxx · 风险 低/中/高。
  • 按钮只有四个:拒绝、会话拒绝、会话允许、允许;Esc等同拒绝。
  • 正文是人话摘要:修改文件/写入文件/运行命令/ 访问网络/搜索内容/使用 <toolName>,并列出最多 3 个关键字段 (文件、将要运行、搜索内容、网址、用途、任务); 底部「查看技术详情」折叠原始 toolInputJSON。
  • 高风险操作不会无限等待:30 分钟无人操作即按拒绝继续,同时推 stream:permission:approval-resolved(reasontimeout)收起卡片, 并往会话时间线写一条agent_error(codePERMISSION_TIMEOUT, 文案如「权限审批「Bash」等待超过 30 分钟已自动拒绝,已跳过该操作」)。会话被取消则写 PERMISSION_CANCELLED。
  • 卡片已失效后再点按钮,主进程返回ok:false,界面提示「该权限请求已失效(等待超时或会话已取消), 你的选择未生效」。
  • 协议里还存在allow-project/allow-global(会落 permission_decisions 表长期记忆),但当前审批卡只暴露上面四个按钮,用户点不到项目/全局授权。
  • Computer Use 有硬闸:computer_direct_action与computer_unknown 永远拒绝; 受管的桌面任务 start/resume 等在入口直接放行,不会弹应用内审批。

3. Rules:注入 prompt 的策略片段

Rules 是存在 SQLite(rules表)里的文本片段,每条包含name/ content/priority/enabled与一个作用域。 入口是 设置 → 规则。

作用域 scope设置页分组行为
systemSystem(SYS)应用内置,只读;只能启用/停用,不能改内容、不能删除。首次会写入两条种子规则「安全约束」「代码风格」
teamTeam(TEAM)团队管理员发布的规则
userUser(USER)用户全局偏好
projectProject(PROJ)可带scopeRef(工作区 id);为空时对所有工作区生效
sessionSession(SESS)会话级临时规则

注意设置页 Project 分组下方的说明文案写的是「.spark/rules · 当前工作区」,但代码里没有任何地方读取.spark/rules/目录:项目规则同样存在rules表里。新增/编辑面板只有「名称 / 内容 / 优先级」三个字段,没有 scopeRef 选择器, 因此从界面新建的 project 规则scopeRef 为空、对所有工作区生效。

真实生效链路(每 turn 组装系统提示词时):

  1. 取所有enabled的system规则。
  2. 取enabled的project规则,且scopeRef 为空或等于当前 primary workspace id。
  3. 取「受管规则」:Agent 配置里勾选的规则(agent.ruleIds)+ Workflow 节点配置里勾选的规则 (节点config.ruleIds),按priority降序。
  4. 以上合并去重后拼成[Runtime Rules]段落注入系统提示词。

三个勾选入口都能在界面上找到:

  • 设置 → 规则:分层列表,System 只读、其余可增删改与启停。
  • Agent 编辑面板 →「规则」区(描述:约束 Agent 的行为与输出),多选ruleIds。
  • Workflow 节点 Inspector →「规则」字段,多选ruleIds。

另外有两条容易混淆的链路:

  • 项目规则文件是独立链路:project-context.service.ts 会读取工作区里的AGENTS.md/CLAUDE.md(以及.claude/、.codex/、.agents/下的同名文件)作为项目上下文,与 rules 表无关。
  • rules:compose(RuleCompositionEngine,作用域优先级 system < team < user < project < session,支持 override/merge)目前只经 IPC 暴露,没有接进 turn 组装链路。 也就是说「按作用域优先级合并」是需要显式调用才成立的能力,不是默认行为。

4. Hooks:两套并存的系统

设置里只有一个「Hooks」入口,但它下面挂着两套互不相同的机制,注意别混用名称。

① 经典 Hooks(通知/提示音)

  • 节点只有四个:permission_request(权限申请)、ask_user_question(Agent 提问)、session_end(Turn 正常结束)、session_fail(运行出错)。
  • 每个节点可分别开关「提示音」与「系统通知」,总开关是enabled,默认全开。
  • 配置存在app_settings的hooks/data(渲染端还同步一份到 localStorage 以即时渲染), 触发走hook:trigger/hook:play-sound /hook:show-notification。
  • 界面位置:设置 → Hooks 页面底部的「经典通知」区块,可逐节点「测试」。

② Hooks V2(生命周期事件 + 动作)

  • 事件名是点号形式,共 7 个:turn.started、permission.requested、question.requested、response.committed、turn.completed、turn.failed、turn.cancelled。
  • 动作目前三类:builtin.notification(系统通知,可模板化 title/body)、builtin.sound(提示音)、tool.invoke(调用统一工具目录里的工具, 保存的是稳定引用sourceKind+sourceId+toolName)。
  • 作用域是application/workspace/agent/ session; 它们是同一上下文的匹配集合,不是继承树,确定性优先级为session > agent > workspace > application。更高优先级可以显式停用低优先级绑定。
  • 绑定安全:绑定时记录授权的executionHash;定义改动后哈希变化,绑定进入 needs_review并不再自动执行,需要重新授权。
  • 执行语义:事件先写入hook_events(outbox),由 dispatcher 派发到 hook_runs队列,worker 每 2 秒轮询领取执行。默认超时 15 秒;重试策略默认 unsafe(失败不重试), 可改为safe/keyed,最多 10 次、指数退避。
  • 运行状态枚举:queued/running/succeeded/ failed/skipped/blocked/cancelled/ outcome_unknown。界面可查看运行记录并重试/取消。
  • 应用级总开关:关闭 = 停止领取 + 尽力取消运行中的动作;无法确认结果的运行落到 outcome_unknown,它不会回滚已经发生的外部副作用。
  • 关键设计:Hook 由宿主确定性地在生命周期事实上触发(permission.requested由 PermissionService 在真实进入等待时发射),不是让模型在提示词里自己决定要不要调 Hook。
  • IPC 前缀是hookV2::定义 CRUD、绑定 upsert、生效列表、运行记录、系统状态与开关、 预览、工具候选、试运行。

5. 用量账本

用量落在 SQLite 表usage_ledger,字段为:session_id、provider_id、model_id、input_tokens、output_tokens、reasoning_output_tokens、cache_read_tokens、cache_write_tokens、cost_usd、request_timestamp、created_at。写入点是 per-turn 的增量核算(session/session-usage-ledger.ts), 按 turn 累计基线去重,避免同一份快照重复入账。

界面入口与能看到什么:

入口内容数据来源
设置 → 用量统计本月概览(请求数 / 输入 / 输出 / 缓存命中 / 缓存写入)、累计统计、用量排行(模型·渠道两个 tab + 时间区间)、最近请求列表usage:get-dashboard、usage:get-by-date-range
设置 → 用量统计 → 数据管理刷新数据;「清理旧记录」删除 90 天以前明细(olderThanDays: 90)usage:purge
设置 → 通用(页面顶部)用量热力图与模型用量趋势卡usage:get-by-date-range
同一张「用量排行」卡片的渠道 tab渠道维度的用量拆分(由模型分组数据在前端二次聚合)usage:get-by-date-range的 modelGroups

协议里还有usage:record与usage:get-session(会话级汇总)。 需要明确的是:当前没有 CSV/JSON 导出,也没有月度预算提醒—— 旧文档写过这两项,代码里找不到对应实现。

6. 审计与排错:现在真实存在的记录

重要事实:没有「设置 → 审计」这一页,也没有统一的全量审计面板。 可追溯性由下面几块拼起来:

  • 本地日志(设置 → 本地日志):读写运行时日志文件,视图可切换 all/canvas(画布任务)/tools(工具运行)三个范围, 按debug/info/warn/error 过滤,支持关键词过滤, 单次最多读 500 行;操作有刷新、导出、在文件夹中显示、清空。对应 IPC:log:read、log:clear、log:reveal。 日志级别与「运行时日志」开关同在设置 → 本地日志页。
  • 会话时间线:审批超时/会话取消会写agent_error事件 (PERMISSION_TIMEOUT/PERMISSION_CANCELLED), 这是回答「Agent 为什么跳过了这一步」的权威位置。
  • 权限记忆表permission_decisions:记录 scope(project/global)、action、tool_name、decision(allow/deny)。目前没有列出它的界面, 排查时只能查库。
  • Hook 运行记录表hook_runs:设置 → Hooks 内可查看与重试/取消。
  • 插件运行时审计表plugin_runtime_audit 与跨会话引用审计表session_reference_audit:各自模块内部使用。

账号同步(设置 → 账号同步)覆盖的是 customCommands / prompts / memory / assistants / workflows / appearance / promptLibrary 这些类目,不包含权限、用量与审计数据; 这些数据都只在本机 SQLite。

7. 常见坑与排查

  • 改了规则却感觉没生效:规则只参与「到达审批时的 allow/ask/deny 判定」, 而工具是否被送到审批回调由会话权限模式(SDK 层)决定。也是历史 bug 的高发点—— 早期审批回调固定forcePrompt: true,导致设置页改的规则从不生效,现在已改为先过当前 Profile 规则判定。
  • 「完全访问」不是更聪明,而是不拦:claude-bypass/codex-full-access/spark-bypass跳过人工审批:Spark 的 spark-bypass界面描述就是「跳过全部审批与规则,完全由 agent 执行」。 设置页在选中 danger 档位时会显示「当前默认策略会跳过人工审批……」的警告。
  • 新会话没继承你刚改的默认策略:runtime-permissions/defaults 只作用于新会话与 无会话输入区;已有会话请在会话内切换权限下拉,或通过 session:update改。
  • 没登记过的第三方工具被当成命令执行类目:动作映射里查不到的工具会兜底为 command_exec,于是「执行 shell 命令」这条规则会意外约束它。别在设置页找 「browser_navigate」这种按工具名的开关——不存在。
  • 会话允许/拒绝重启就没了:这两个决策只在内存中生效;跨重启的长期记忆只有 project/global 两档(permission_decisions表)。
  • 长时间离开导致任务被跳过:审批 30 分钟自动按拒绝处理,任务会继续走但跳过该操作, 事后到会话时间线看 PERMISSION_TIMEOUT记录即可确认。
  • 以为.spark/rules/里的文件会被读取:不会。项目规则请用设置 → 规则, 或直接写工作区的AGENTS.md/ CLAUDE.md(那是项目上下文链路)。
  • Hook 定义改了之后不再执行:这是有意的安全设计——executionHash 变化会让绑定进入needs_review,需要在绑定里重新授权。

8. 最佳实践

  • 日常开发用project-standard+ 对应适配器的「自动编辑 / 按需批准」;command_dangerous保持ask-twice,删除类命令自然会撞上双重确认。
  • 团队统一约束不要写在.spark/rules/:用team作用域规则, 或者把约定写进仓库的AGENTS.md(项目上下文会读取,且随 git 走)。
  • 需要「某个 Agent 只额外遵守这几条规则」时,用 Agent 编辑面板的「规则」多选,而不是给全局加规则; Workflow 节点同理,用节点 Inspector 的「规则」字段。
  • Hook 的tool.invoke 动作会真实触发外部副作用,且应用级开关关闭时不回滚已发生的动作; 先用界面上的「试运行/预览」验证条件与映射。
  • 用量清理是有损操作(删除 90 天以前的明细)。需要长期留存请在清理前用数据库备份(设置 → 存储与备份)。
  • 排查顺序建议:会话时间线(是不是被跳过/超时)→ 本地日志(tools 范围看工具调用)→ 权限 Profile 与 会话权限模式(为什么被拦/为什么没弹卡)→permission_decisions 表(是不是被记住放行了)。

速查表

项目内容
权限模式(claude)claude-ask 请求批准 / claude-plan 计划模式 / claude-auto-edits 自动编辑 / claude-auto 自动审批 / claude-bypass 完全访问
权限模式(codex)codex-default 按需批准 / codex-auto-review 替我批准 / codex-full-access 完全访问
权限模式(spark)spark-default 手动审批 / spark-auto 自动审批 / spark-bypass 完全访问
规则模式allow 允许 / ask 询问 / ask-twice 双重确认 / deny 拒绝
权限 Profilestrict 一切都问 / project-standard 工作区写入自动允许(默认) / trusted 自动允许大多数
动作类目file_read · file_write · command_exec · command_dangerous · network_known · network_unknown · mcp_tool
审批超时30 分钟,超时按拒绝处理并写 PERMISSION_TIMEOUT
Rules 作用域system / team / user / project / session(规则存 SQLite,不读 .spark/rules/)
Hook V2 事件turn.started · permission.requested · question.requested · response.committed · turn.completed · turn.failed · turn.cancelled
Hook 作用域application / workspace / agent / session,优先级 session > agent > workspace > application
用量表usage_ledger(tokens / 缓存读写 / cost_usd / request_timestamp)
排错入口设置 → 本地日志(all / canvas / tools,最多 500 行)+ 会话时间线 agent_error

上手清单:给一台新机器配好权限治理

从默认策略、Profile 到规则与排错入口,一次配齐

  1. 打开 设置 → 权限策略,在「SDK 执行默认策略」里选默认执行器(Claude SDK / Codex / Spark)与默认权限策略(新会话生效)
  2. 在同一页的「权限 Profile」里选中 project-standard;把 command_dangerous 保持「双重确认」,按团队要求调整 file_write / network_unknown
  3. 打开 设置 → 规则,在 User 或 Project 分组新增一条规则(名称 + 内容 + 优先级);System 分组只读,不要指望能改
  4. 如果只想让某个 Agent 遵守这几条规则,去 Agent 编辑面板的「规则」区勾选;Workflow 场景在节点 Inspector 的「规则」字段勾选
  5. 需要事件回调时打开 设置 → Hooks,用 Hooks V2 建定义(选 permission.requested 或 turn.failed,动作选 builtin.notification),再在绑定里授权 executionHash
  6. 验证:跑一个会写文件的任务,确认审批卡出现/不出现的时机符合预期;随后在 设置 → 本地日志(tools 范围)与会话时间线确认记录

常见问题

「请求批准 / 自动编辑 / 完全访问」和设置里的「允许 / 询问 / 双重确认 / 拒绝」是什么关系?

前者是会话权限模式(SDK 层,按适配器不同,例如 claude-ask / claude-auto / claude-bypass),决定工具调用是否被送进审批回调;后者是权限 Profile 里的动作类目规则,决定到达审批时是自动放行、自动拦截还是弹卡。两层叠加生效。

审批卡上的「会话允许」会写进数据库吗?

不会。会话允许/会话拒绝只写内存,重启应用即失效;会落库的是 project/global 两档记忆(permission_decisions 表),但当前审批卡没有提供项目/全局授权按钮。

审批一直不点会怎样?

30 分钟后按拒绝继续执行,卡片会自动收起,并在会话时间线写入一条 agent_error(PERMISSION_TIMEOUT),文案说明等待了多少分钟、跳过了哪个工具。

Rules 的 System / Team / User / Project / Session 是继承关系吗?

不是。Rules 的作用域只是存储与分组维度:每 turn 实际注入的是「启用的 system 规则 + 启用的 project 规则(scopeRef 为空或等于当前工作区)+ Agent 勾选的规则 + Workflow 节点勾选的规则」。带优先级合并语义的 rules:compose 是另一条未接入 turn 链路的 IPC 能力。

Hooks 里怎么在权限弹卡时发通知?

用 Hooks V2 的 permission.requested 事件配 builtin.notification 动作(经典 Hooks 的 permission_request 节点只能开提示音/系统通知,不能带条件判断)。注意 Hook 定义改动后绑定会进入 needs_review,需要重新授权才继续执行。

用量数据能导出吗?能设置月度预算提醒吗?

当前都不能。用量只在设置 → 用量统计与设置 → 通用的热力图/趋势卡里查看,数据管理只有「刷新」与「清理 90 天以前明细」。需要长期留存请用设置 → 存储与备份做数据库备份。