# dsh-decision-engine 配置模板
#
# 用法：复制到数据目录，按需修改。
#   mkdir -p ~/.dsh/dsh-decision-engine
#   cp config.example.yml ~/.dsh/dsh-decision-engine/config.yml
#
# 文件名是 config.yml：本插件早已不是「只服务 startlux 一家」的插件，
# 而是任意决策服务（system one / 硅基流动 / custom）的决策层，
# 名字里再挂某个服务商只会误导。旧名 startlux.yml 仍会被自动读取并迁移，
# 升级不需要重新填 key。
#
# 插件按以下顺序找配置，取第一个存在的：
#   ~/.dsh/dsh-decision-engine/config.yml          （首选位置）
#   ~/.dsh/dsh-decision-engine/startlux.yml        （旧名，首次启动自动改名）
#   ~/.dsh/dsh-startlux-decision/startlux.yml      （上一代数据目录；自动拷到新目录）
#   ~/.dsh/plugins/dsh-startlux-decision/startlux.yml
#   ~/.dsh/plugins/dsh-startlux-guard/startlux.yml
#
# 数据目录（插件自动读取）：
#   <上述目录>/config.yml          配置
#   <上述目录>/telemetry.jsonl     决策记录（人读的流水账）
#   <上述目录>/stats.jsonl         统计记录（配置页「统计与对比」读它）
#   <上述目录>/analytics.json      匿名统计的本地状态（安装 id，可删除以重置）
#
# stats.jsonl 记的是按 provider × model 分开的 token 与耗时，用于统计与
# 节省量对比；telemetry.jsonl 不含 provider 与 output token，满足不了统计
# 所需，因此两者分开存。两者都是追加写的 JSON Lines，可直接 tail 查看。
#
# 改完立即生效：每次工具调用 / 每一步前都会检查 mtime，无需重启 DSH。

# 总开关：true 才开始工作（默认 false，不会擅自介入你的会话）
enabled: false

# shadow  = 只记录判断结果，绝不拦截（建议先用它观察一段时间）
# enforce = 真正执行各决策点的处置
#
# 注意：这一项现在只是「工具授权」这一个决策点的历史开关。
# 新写法是给每个决策点单独指定模式，见下面的 pointXxx。
# pointAuthorize 留 auto 时，就沿用这里的 mode（shadow→observe、enforce→enforce）。
mode: shadow

# ============================================================================
# 服务提供方（provider）
# ============================================================================
# 四选一：
#   systemone         内置（默认）。任意 System One 协议服务 POST /v1/systemone，
#                     默认指向 TypeSafe（https://api.typesafe.ai/v1）。
#   siliconflow       内置。硅基流动（国内站）https://api.siliconflow.cn/v1/systemone
#   siliconflow-intl  内置。Silicon Flow 国际站 https://api.siliconflow.com/v1/systemone
#                     （与国内站是两套账号，key 互不通用）
#   custom            自己填 base URL，接任何提供决策端点的服务
#                     （自建 llama.cpp / vLLM、企业网关、第三方中转）。
#
# 不写这一项 = systemone。
# 老配置里写的 provider: bamboonode 会在加载时自动迁移到 custom（DEC-031）：
# 用户自己填过的地址 / 模型 / key 原样搬过去；若从未改过默认地址，插件不替
# 用户挑新地址，而是在设置页明确报「不可用」，等用户自己填。
#
# siliconflow / siliconflow-intl 是「开箱即用」的：地址、路径、默认模型都已
# 预置，只要粘一个 API key 就能跑（详见下方「密钥」一节）。
provider: systemone

# ---- 遗留字段（DEC-031：bamboonode 预设已移除）------------------------------
# 下面的 baseUrl / model 是 bamboonode 时代的历史字段，解析点不再读取，
# 只在加载时作为迁移来源被搬到 custom 的对应字段。新配置不要再写它们。
#
# baseUrl: https://your-own-gateway.example.com/v1   （会搬到 customBaseUrl）
# model: your-model-name                             （会搬到 customModel）

# ---- 模型（按 provider 分开存，切换时互不覆盖）------------------------------
# system one 的模型。留空 = 服务端默认（jev-latest）。
systemoneModel: ""

# 硅基流动 / Silicon Flow 的模型。**留空 = 用预设默认 Kev-4B**——
# 这两个端点把 model 列为必填（实测省略或空串都回 400
# "Model field is required."），所以这里的「留空」不是「让服务端挑」，
# 而是用预设默认。想换模型就填 SemIf（官方目前只支持这两个决策模型）。
#
# 你写的 kev-4b 与 Kev-4B 是同一个模型：实测该端点对大小写不敏感
#（三种写法都 200），/v1/models 里的规范写法是 Kev-4B。
siliconflowModel: ""
siliconflowIntlModel: ""

# ---- custom 预设（provider=custom 时才生效）--------------------------------
# base URL 必填，且必须是 http/https 地址；不带 scheme 会被拒绝（内置预设
# 允许省略 scheme，custom 不允许——自定义地址必须显式，避免猜错协议）。
# customBaseUrl: https://api.example.com/v1
#
# 端点风格（**首选**路径，不是唯一路径）：
#   auto       地址已带 /decisions 或 /systemone 就原样用，否则先试 decisions
#   decisions  先试 POST <base>/decisions
#   systemone  先试 POST <base>/systemone
#
# 路径后缀由**服务商**决定，不由模型决定。实测（2026-10-25，真实端点）：
#   SiliconFlow 的 Kev-4B   /v1/decisions → 404，/v1/systemone → 200
#
# 所以首选路径返回 404 时，插件会自动改用另一条再试一次，并记住试通的那条。
# 只有 404 会触发换路径：400（模型/内容不合法）与 401（key 不对）都跟路径无关。
customEndpointStyle: auto
#
# custom 自己的模型（留空 = 服务端默认）。与内置预设的 model 分开存，
# 这样在两者之间来回切换不会互相覆盖。
customModel: ""
# custom 的 key；留空则读 customApiKeyEnv（环境变量或 .credentials.yaml 的 refs）
customApiKey: ""
customApiKeyEnv: ""
# 附加请求头，格式 `名称: 值`，多项用分号分隔。
# authorization / content-type / host 由插件固定，写了也会被忽略。
customHeaders: ""

# ============================================================================
# 密钥
# ============================================================================
# 每个 provider 有**自己的** key 名，各自保存各自的 key：
#
#   system one          -> TYPESAFE_API_KEY
#   硅基流动（国内站）   -> SILICONFLOW_API_KEY
#   Silicon Flow 国际站 -> SILICONFLOW_INTL_API_KEY
#   custom              -> customApiKeyEnv（没填时用 DECISION_ENGINE_CUSTOM_KEY）
#
# 四者互不覆盖，可以同时配好；切换 provider 不会串用别人的 key。
# 老安装存在 BAMBOO_API_KEY 名下的 key 不受影响：迁移到 custom 后，密钥变量名
# 会自动指回它（DEC-031）。
# 两个 SiliconFlow 站点是**两套账号**，key 不通用，因此各占一格——
# 共用一格的话，切过去就会拿错 key 得到 401，而用户看不出是拿错了还是失效了。

# 密钥存到哪里：
#   credentials = ~/.dsh/.credentials.yaml 的 refs 段（默认，与其他服务统一托管）
#   config      = 本文件（自包含，便于随目录备份；注意这里是明文）
keyStorage: credentials

# 变量名可以覆盖（留空即用上面的默认名）：
# systemoneApiKeyEnv: ""
# siliconflowApiKeyEnv: ""
# siliconflowIntlApiKeyEnv: ""
# customApiKeyEnv: ""

# 直接写明文 key 也可以（各 provider 一格，互不影响）。
# 若 keyStorage=credentials，在设置界面粘贴的 key 会写进 .credentials.yaml，
# 不会落到这里；只有 keyStorage=config 时下面这些字段才会被写入。
# systemoneApiKey: ""
# siliconflowApiKey: ""
# siliconflowIntlApiKey: ""
# customApiKey: ""

# .credentials.yaml 里的形态就是 refs 段：
#   refs:
#     TYPESAFE_API_KEY: sk-另一个key
#     SILICONFLOW_API_KEY: sk-国内站的key
#     SILICONFLOW_INTL_API_KEY: sk-国际站的key

# ============================================================================
# 服务端下发（DEC-030）
# ============================================================================
# 打开后，地址 / 模型 / key 由内置端点 https://dsh.zuos.us/api/provider-config
# 下发并直接使用。**地址不可配置**是刻意的：它就是「开箱即用」的全部含义。
#
# 关于下发的那把 key（重要）：
#   - 只保存在插件进程的内存里，**永不落盘**：不写本文件、不写 .credentials.yaml、
#     不进遥测 / 统计 / 决策明细；任何接口只回传末四位（…abcd）。
#   - 进程退出即消失，下次启动重新获取（下发带 TTL，默认 600 秒）。
#   - 关掉这个开关会立刻把它从内存里清掉。
#
# 服务端不可用时（404 / 403 / 超时 / 载荷非法 / 已过期且刷新失败）**静默回落到下面的
# 本地配置**（包括你自己填的 key），配置页显示一行原因，不会中断守卫。
#
# 手动填 key 的整套流程完全不变；服务端生效期间本地值只是暂不生效，关掉开关立刻恢复。
#
# 可选门禁：Worker 侧若设了 PROVIDER_CONFIG_TOKEN，本机要能读到同一把令牌——
# 优先环境变量 DECISION_SERVER_CONFIG_TOKEN，其次下方 .credentials.yaml 的 refs 段同名键。
serverProvider: false

# 超时预算（毫秒）。超时自动降级放行，连续失败进入冷却。
timeoutMs: 8000

# 思考模式（startlux 的 think_mode），三档：
#   never  不思考——最省延迟（实测约 1.9s vs 7.2s），默认值，热路径首选
#   auto   仅在模型自己判断需要时思考——折中
#   always 总是思考——判断更充分，但每次工具调用约多等 5 秒
# 该端点 output_tokens 恒为 0，思考开销不计入计量，所以省的主要是延迟。
thinkMode: never

# ============================================================================
# 决策模型参与点
# ============================================================================
# 三态：off 不参与 / observe 只记录不干预 / enforce 真正生效。
# 九个点全部已实装。新点默认 off —— 升级不会改变现有行为。
#
# 建议：先 observe 跑一段时间，对着 telemetry.jsonl 确认与人工判断一致，
# 再逐点切 enforce。热路径的点（route / delegate / cotTranslate）尤其如此。

# ---- 1. 工具授权：每次工具调用前判断能否放行 -------------------------------
# 替代 auto-review——后者把完整会话历史发给主模型，每次调用都跑一遍。
pointAuthorize: auto

# ---- 2. 模型路由：机械步骤下沉到便宜模型 -----------------------------------
# 热路径。三道闸控量：每轮最多 routeMaxStepsPerTurn 步、路由未变不问、
# 新用户输入后的步骤不问。
# 必须同时配好 routeCheapProvider + routeCheapModel 才会真的换模型；
# 两者留空时即便打开此点也只会记录「本该换」。
pointRoute: off
routeCheapProvider: ""
routeCheapModel: ""
# routeMaxStepsPerTurn: 3
# routeMaxScore: 0.4        # 能力需求归一化后超过此值就保留强模型
# routeMechanicalFloor: 0.6 # 「机械步骤」的最低置信
# routeRiskFloor: 0.3       # 风险达线一律保留强模型

# ---- 3. 子任务分派：接着做 / 开 subagent / 组 agent team -------------------
# 热路径。只在每轮第一步且带新用户输入时判断（分派只在这一刻可能改变）。
# 生效时注入一条提示，不替模型决定——工具选择仍归模型。
pointDelegate: off
# delegateConfidenceFloor: 0.5  # 三选项均分 0.33，实测需 ≥0.5 才算有倾向
# delegateBlockingFloor: 0.6    # 被本地事实阻塞时继续内联
# delegateParallelFloor: 0.5    # 组队还需真并行才有意义

# ---- 4. 上下文压缩：摘要 vs 只丢陈旧工具结果 -------------------------------
# 非热路径。只在实测 token 达触发线（compactThresholdRatio × 窗口）才问。
# 生效方式：先跑宿主的确定性工具结果裁剪；compaction-basic 随后重新测压，
# 低于阈值就跳过摘要——这才是真正省下的那一次调用。
pointCompact: off
# compactContextWindow: 0     # 0 = 用 compactTriggerTokens 绝对阈值
# compactThresholdRatio: 0.7  # 提前于 compaction-basic 的 0.8 介入
# compactTriggerTokens: 100000
# compactToolResultChars: 8192
# compactDistilledFloor: 0.6  # 结论已落地 → 允许只裁剪
# compactVerbatinFloor: 0.6   # 仍需逐字原文 → 一律摘要（安全否决优先）

# ---- 5. 轮次延续：再来一轮有没有增量价值 -----------------------------------
# 非热路径。生效方式是用 agent.steer() 续一轮，每轮最多 turnStopMaxSteers 次。
pointTurnStop: off
# turnStopMaxSteers: 1
# turnStopClosedFloor: 0.6
# turnStopUnfinishedFloor: 0.6
# turnStopValueFloor: 0.35    # 只作弱确认；主判据是 unfinished

# ---- 6. 检索必要性：这条信息是不是已经知道 ---------------------------------
# 非热路径。只对检索类工具生效，其余工具零开销。
pointRetrieval: off
# retrievalTools: [web_search, web_fetch, research, multi_search, advanced_search]
# retrievalCoveredFloor: 0.5     # 上下文已覆盖该事实
# retrievalWouldAddCeiling: 0.8  # 且再搜基本没有新信息

# ---- 7. 技能选择：该装哪个技能 ---------------------------------------------
# 非热路径。只对 skill 工具、且候选 ≥2 时判断。
# 生效时拦下不匹配的加载并指路，避免「装错再换」付两遍正文 token。
pointSkillPick: off
# skillPickCandidates: 8      # 送给模型挑选的候选上限（只带名称+描述）
# skillPickConfidenceFloor: 0.5

# ---- 8. 思维链翻译门槛：这条内容真的需要翻译吗 -----------------------------
# 热路径（每条消息）。本地先做免费预筛（去代码块/URL 后判断有无正文），
# 预筛不过的直接跳过，不发请求。生效时判为「不需要」则返回原文。
pointCotTranslate: off
# cotProseFloor: 0.5
# cotSameLanguageFloor: 0.8

# ============================================================================
# 自身开销护栏
# ============================================================================
# 决策模型自己也烧 token（实测约 1.9k/次，p50 约 0.5s），靠缓存挡住重复调用：
# 命中缓存就不发请求。本插件不限制每小时的总用量，因此没有预算字段。
# 「同类复用」软层默认关闭（fail-closed），工具授权点永久禁用；
# cacheSize / cacheTtlMs 同时约束精确层与（显式启用后的）软层。

# ---- 以下为可选调优项 ----
# cacheSize: 256            # 缓存条数上限
# cacheTtlMs: 600000        # 裁决存活时间
# recentMessages: 4         # 带给模型的上下文条数
# guardReadOnlyTools: [read, grep, glob, todo_write]
# guardDenyConfidence: 0.85
# guardIrreversibleCeiling: 0.2
# 低风险且可能不可逆的调用，只有当「服务于当前任务」的概率低于此线才升级
# 人工确认；设 0 可整体停用该分支，设 1 恢复旧行为（只看不可逆分数）。
# guardTaskRelevanceFloor: 0.5

# ============================================================================
# 匿名使用统计（可选）
# ============================================================================
# 只上报「装了多少台、每天有多少人在用」，用来改进插件。字段只有七个：
# 匿名安装 id（首次运行随机生成的 UUIDv4，与账号无关）、插件版本、平台、
# 架构、宿主版本、当日决策数、事件类型。
#
# 绝不上报：API key、prompt / 工具参数、文件路径、provider 地址、会话内容、
# 用户名；服务端也不记录 IP。删掉 analytics.json 即换新身份。
#
#   off      完全联网都不做（连版本检查都没有）
#   install  仅首次运行上报一次「装了」
#   daily    每天上报一次，附带当日决策数（默认）
#
# telemetryLevel: daily
# telemetryEndpoint: ""     # 留空用内置端点

# ============================================================================
# 决策历史（DEC-022）——会话里的「决策历史」标签页
# ============================================================================
# 开启后，会话标签栏会在「对话」「轨迹」之后多出一个「决策历史」标签页，
# 按时间列出**本会话**里经本插件做出的每一次决策：决策点、provider、
# model、裁决、耗时、token，展开还能看到发给决策模型的现场（state）、
# 问题（questions）与它回来的回答（answers）。
#
# 数据只落在本机 $DSH_HOME/dsh-decision-engine/decisions.jsonl，
# **不经过任何网络请求**，也**不进**上面的匿名统计。
#
# 关掉之后**完全不写这个文件**（不采集内容、不占磁盘，标签页也随之消失）
# ——不是「写了藏起来」。明细里可能有会话正文与命令片段，用户说不记录
# 就该真的不落盘。
#
# decisionsTabEnabled: true      # 默认开启；**这是唯一的开关**（DEC-026 / DEC-029）：
#                                开 = 记录明细，且**一定记录决策内容**；
#                                关 = 完全不写 decisions.jsonl（见上），
#                                没有「只存元数据」的中间档。
#
# 下面两项是**高级档位**，配置页上没有对应的开关：需要时手改本文件。
# （一个开关管「这个功能要不要用」就够了；「记多细、最多占多大」是要主动
# 去调的旋钮，不该天天摆在配置页上。）
#
# 单条内容的上限（字符）。超出时依次裁剪：工具参数预览 → 最近对话摘要 →
# 整个 state；只有连「问题 + 回答」都装不下时才退回只留元数据。
#
# decisionsMaxContentChars: 4000
#
# 明细文件体积上限（字节）。到顶轮转成 decisions.jsonl.1（只留一代），
# 因此磁盘占用最多约两倍上限。
#
# decisionsMaxBytes: 8388608   # 8 MB
