Bundle
dsh-citation-auditor
Citation Auditor — 审查 AI 回复里的引用来源:扫描、评分、出报表,只读不拦截
- Source
- AndKinoko
- stars
- 2 stars
- License
- MIT
- Updated
- Updated 13 hours ago
Readme
# Citation Auditor
<img width="1280" height="704" alt="大肥鱼" src="https://github.com/user-attachments/assets/700a5a9e-b5ae-4156-8357-08daf81b34b4" />
审查 AI 回复里的引用来源:把回复中的 URL 提取出来,按威胁度打分排序,输出报表;**拦截名单里的域名会被真正禁止访问**(web_search / web_fetch 等 web 工具直接拒绝)。名单完全归用户维护,处置权在你手上。
[Cordis](https://github.com/deepseek-ai/deepseek-harness) 函数插件,可被 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 直接加载。业务内核(scanner / scorer / report)是纯函数,脱离 dsh 也能复用。
## 工作效果
### 打分机制(当前打分机制非常简单,打分机制的细化为todo事项,我暂时没有什么好的想法,欢迎提issue)
| 模式 | 打分函数 | 判断维度 | 核心规则 | 典型分值 |
| --- | --- | --- | --- | --- |
| whitelist | scoreWhitelist | 纯名单二元判定 | 命中白名单 → trusted;否则一律 critical | 0 / 100 |
| simple | scoreSimple | 域名注册年龄 | 注册于 2023 年前 → trusted;2023 年及以后 → critical;查不到年龄 → critical(treatAsNew) | 0 / 100 |
| normal | scoreNormal | 多信号累加 | TLD 拦截、连字符/数字、长度、注册年龄、年龄可验证性分别累加 | 0–100 |
<img width="1541" height="820" alt="QQ20260903-011153" src="https://github.com/user-attachments/assets/7d8082ae-d209-4af4-9538-3c118bb4f9ae" />
### 拦截成功的效果如图
<img width="1556" height="730" alt="QQ20260903-011055" src="https://github.com/user-attachments/assets/b1ee50ba-b40d-4875-ad97-e29c531d8e89" />
## 工作方式
扫描文本 → 提取 URL → PSL 解析注册域(eTLD+1)→ 0–100 打分 → 出报表。
三种模式:
- **白名单模式**:名单内绿,名单外全红。最严。
- **简单模式**:2023 年前注册的可信,之后标红。
- **普通模式**:多信号综合评分(TLD、连字符/数字个数、域名长度、注册年龄)。
命中拦截名单的域名直接标红,不参与评分;精确域名与 `.xyz`/`.shop` 这类 TLD 条目都受**当前模式**的拦截名单开关控制(开关关闭 = 该模式完全不应用名单)。域名匹配统一按注册域:`en.wikipedia.org` → `wikipedia.org`。年龄查不到时只记"可疑"(ageQuery 未启用或查询失败),只有确认是新域名才从严标红。
**真实拦截**:拦截名单(含 `.shop` 这类 TLD 条目)会在 `tools/pre-execute` 阶段拦截 web 工具(web_search / web_fetch 等),命中即拒绝调用并告知模型原因——加入名单后模型就访问不了该网页了。只拦 web 工具;pwsh/curl 等 shell 通道属沙箱层,不在拦截范围。名单增删(界面按钮或直接改 blocklist.json)即时生效,无需重启。
向 dsh 注册两个工具:
| 工具 | 作用 |
|---|---|
| `citation_audit` | 审计一段文本,返回结构化结果 + 纯文本报表 |
| `citation_manage` | 查状态、启停、切模式、增删名单、测试年龄片段 |
## Web 界面
- **设置页卡片**:三种模式单选、各模式拦截名单开关、白名单免查加速、插件总开关、年龄片段 [编辑JS代码] 与 [测试](固定 wikipedia.org)。名单旁的 [查看/编辑] 用系统默认程序打开对应 JSON,改完即生效。
- **交互报表**:会话里 `citation_audit` 渲染成可点报表,非可信域名带 [➕ 加入拦截名单] / [➕ 加入白名单] / [忽略];拦截名单开关关闭时弹窗三选(开启并保存 / 仅保存 / 取消)。写入后按钮变灰,可再点确认移除。
- **悬浮窗**:右下角可拖动球(有可疑域名时显示红色角标),点击展开最近回复的网址列表与分数;面板内含 ⚙ 设置入口和同样的名单按钮。悬浮窗按宿主实际能力取会话节点:新宿主优先订阅 `uiConversation` 的 chat 通道(`legacy.nodes`),旧宿主回退会话快照自带的 nodes。
所有设置/名单读写都走插件自有的同源端点 `/api/citation-auditor/*`(status / audit / list / settings / test-age / open-file),直接落盘 `settings.json`,不依赖 harness 的 settings 服务。
## 年龄查询片段(唯一联网点)
一段你自己写的 JS,在 worker 线程执行,5 秒超时直接 terminate(同步死循环也拖不垮主线程)。契约:必须返回 `{ creationDate: <ISO 日期或毫秒> }` 对象,查不到返回 `undefined`。先测试后启用,默认关闭。
按 TLD 分发注册局直连的 RDAP 示例(可直接粘贴进 `ageQuery.js`):
```js
async (domain, fetch) => {
const tld = domain.split('.').pop();
const regs = {
com: 'https://rdap.verisign.com/com/v1/domain/',
net: 'https://rdap.verisign.com/net/v1/domain/',
org: 'https://rdap.publicinterestregistry.org/rdap/domain/',
top: 'https://rdap.centralnic.com/domain/',
xyz: 'https://rdap.centralnic.com/domain/',
};
const base = regs[tld] || 'https://rdap.org/domain/';
try {
const res = await fetch(base + domain, { signal: AbortSignal.timeout(4000) });
if (!res.ok) return undefined;
const j = await res.json();
const ev = (j.events || []).find((e) => e.eventAction === 'registration');
return ev ? { creationDate: ev.eventDate } : undefined;
} catch { return undefined; }
}
```
局限:RDAP 只认注册域(已按 PSL 归一化,天然满足);.cn 等部分 ccTLD 无公开 RDAP 或被网络阻断会查不到;新 gTLD(如 .top)数据不一定齐全。
## 数据与安全
名单、设置、年龄缓存各自存成独立 JSON 文件(默认 `~/.citation-auditor/`,配置 `statePath` 可改),外加可编辑的 `ageQuery.js`。写入走临时文件 + fsync + rename 原子发布;损坏先备份为 `*.corrupt-*` 再处理,绝不覆盖;每次读取检查 mtime,直接改文件保存即生效。旧版单文件 state.json 首次启动自动迁移。
报表永不改写 AI 原文;全项目唯一的联网点是你的年龄查询片段。
## 构建与安装
```bash
npm install
npm run typecheck # tsc --noEmit
npm run test # 构建 + node:test(50 项)
```
或 `dsh plugin --profile web add https://github.com/AndKinoko/DSH-Search-Citation-Auditor/releases/download/v0.2.1/dsh-citation-auditor-0.2.1.tgz`(`prepare` 脚本自动构建)。配置只有一个字段:
```yml
- insert:
- id: dsh-citation-auditor
name: dsh-citation-auditor
config:
statePath: "" # 留空用 ~/.citation-auditor
```
client 半边要求宿主 `dsh.engines.dsh >= 0.1.1-rc.1`(host 工具无此门槛)。
## 目录结构
```
src/
├── index.ts # Cordis 插件入口(name/inject/Config/apply + 两个工具)
├── storage.ts # 目录化键值存储(每 key 一文件 + mtime 热重载 + 原子写)
├── routes.ts # /api/citation-auditor/* 数据端点
├── settingsSection.ts # 设置命名空间 host 半边
├── client/ # 浏览器半边(设置卡片 / 交互报表 / 悬浮窗)
├── test/ # node:test 一致性测试
└── auditor/ # 业务内核(scanner/scorer/report 纯函数 + rules/cache/ageQuery)
```
## License
MIT,见 [LICENSE](./LICENSE)。
Install
dsh plugin --profile web add github:AndKinoko/DSH-Search-Citation-Auditor
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-citation-auditor from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.