安全与排障文档(必读)
- 配置/Token 管理 → 本文件「🚀 首次配置」章节
- 错误排查 → references/troubleshooting.md
- 安全约束与审计 → SECURITY.md
Contract Rule Review
合同规则审阅助手:
- 上传桌面合同文件到 AnyShare 个人文档库
- 获取审阅模板(默认使用内置模板,支持用户自定义模板)
- 调用
__规则审阅__1技能进行审阅 - 保存审阅结果到「合同审阅/日期/合同名称/」目录结构并生成分享链接
🚀 首次配置
术语说明
| 术语 | 含义 |
|---|---|
| Access Token | AnyShare 用户访问令牌,用于 API 身份认证 |
| 个人文档库 GNS | 个人文档库的 GNS 路径标识 |
Step 1: 配置 MCP 服务与 Token
本技能依赖 anyshare-asmcp MCP 服务,Token 须预先配置到 ~/.openclaw/workspace/config/mcporter.json:
{
"mcpServers": {
"anyshare-asmcp": {
"type": "Streamable",
"url": "https://anyshare.aishu.cn/asmcp/",
"headers": {
"Authorization": "Bearer <your_token_here>"
}
}
}
}
获取 Token:登录 AnyShare Web → 右上角头像 → MCP授权凭证 → 复制令牌。
Step 2: 验证连通性
mcporter call anyshare-asmcp.doc_lib_owned
返回文档库列表即表示认证成功。
Step 3: 配置 OpenClaw 运行时超时(针对长耗时调用)
编辑 ~/.openclaw/config.toml,在 [skills] 或 [skills.entries.contract-rule-review] 下添加:
[skills.entries.contract-rule-review]
env.MCPORTER_CALL_TIMEOUT = "600000" # 10 分钟,毫秒
或通过 gateway config.patch 更新。
Step 4: 确认个人文档库 GNS
首次运行后,技能会自动将个人文档库 GNS 记录到日志输出中(PERSONAL_DOC_LIB_GNS: gns://...),后续无需重复配置。
目录结构设计
个人文档库/
└── 合同审阅/ ← 主目录(复用已存在的)
└── 2026-04-27/ ← 日期目录(每天新建)
└── 储能电站合同/ ← 合同名称目录
├── 合同原文/ ← 上传的原始合同文件
│ └── 储能电站能源管理合同.docx
└── 审阅报告.md ← 生成的审阅报告
严格遵守:合同原文必须上传到「合同原文/」子目录,审阅报告直接放在合同名称目录下。
模板配置
技能支持两种模板获取方式:
| 优先级 | 模板类型 | 来源 | 说明 |
|---|---|---|---|
| 优先 | 内置模板 | API 获取 | 调用 __规则审阅__1 内置模板接口 |
| 备用 | 本地模板 | 本地 .md 文件 | 模板获取失败时的兜底方案 |
| 用户自定义 | 自定义模板 | 用户提供 | 用户指定模板时优先使用用户模板 |
方式一:API 获取内置模板(推荐)
# 获取内置规则审阅模板
TEMPLATE_RESPONSE=$(curl -s -X GET \
"https://anyshare.aishu.cn/api/intelli-search/v1/skills/templates/default?skill_name=__规则审阅__1" \
-H "authorization: $ACCESS_TOKEN" \
-H "content-type: application/json")
# 解析模板内容
TEMPLATE_CONTENT=$(echo "$TEMPLATE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('template',''))" 2>/dev/null)
TEMPLATE_SOURCE="内置模板(API)"
if [ -z "$TEMPLATE_CONTENT" ]; then
echo "⚠️ API获取模板失败,尝试本地模板..."
# 落入本地模板逻辑
else
echo "✅ 使用内置模板(API获取)"
echo " 模板长度: ${#TEMPLATE_CONTENT} 字符"
fi
方式二:本地模板读取(兜底)
# 读取本地模板
TEMPLATE_DIR="$HOME/.openclaw/skills/contract-rule-review/templates"
case "$CONTRACT_TYPE" in
"采购"|"采购合同")
TEMPLATE_FILE="$TEMPLATE_DIR/采购合同审阅模板.md"
;;
"服务"|"服务合同")
TEMPLATE_FILE="$TEMPLATE_DIR/服务合同审阅模板.md"
;;
*)
TEMPLATE_FILE="$TEMPLATE_DIR/通用合同审阅模板.md"
;;
esac
if [ -f "$TEMPLATE_FILE" ]; then
TEMPLATE_CONTENT=$(cat "$TEMPLATE_FILE")
echo "✅ 使用本地模板: $(basename $TEMPLATE_FILE)"
else
echo "⚠️ 本地模板不存在,使用默认模板"
TEMPLATE_CONTENT="请按照通用合同审阅标准进行审阅"
fi
工作流程
用户请求 ──→ 获取模板 ──→ 创建目录结构 ──→ 上传合同原文 ──→ temporary-area ──→ index-check轮询 ──→ smart_assistant ──→ 保存审阅报告 ──→ 生成分享链接
│ ↓ ↓ ↓ ↓ ↓ ↓ ↓
API获取内置 dir_create file_osbegin file_osendupload POST index-check API file_osbegin file_sharedlink
失败则本地 (4级目录) → file_osendupload temporary-area (最多600秒) file_osendupload _realname_create
完整调用示例
#!/bin/bash
# ========== 配置 ==========
# Token 须预先配置到 ~/.openclaw/workspace/config/mcporter.json
# 以下变量由 mcporter 自动注入,无需在此硬编码
# ACCESS_TOKEN 由 mcporter call 工具通过 --access_token 参数传递
PERSONAL_DOC_LIB_GNS="your_personal_doc_lib_gns_here"
LOCAL_FILE="~/Desktop/合同/采购合同.docx"
CONTRACT_TYPE="采购合同"
FILE_NAME=$(basename "$LOCAL_FILE")
FILE_SIZE=$(stat -f%z "$LOCAL_FILE")
TODAY_DIR=$(date +%Y-%m-%d)
echo "=========================================="
echo "📋 合同规则审阅 v1.8.2"
echo "=========================================="
# ========== Step 1: 获取模板 ==========
echo ""
echo "📄 获取审阅模板..."
TEMPLATE_RESPONSE=$(curl -s -X GET \
"https://anyshare.aishu.cn/api/intelli-search/v1/skills/templates/default?skill_name=__规则审阅__1" \
-H "authorization: $ACCESS_TOKEN" \
-H "content-type: application/json")
TEMPLATE_CONTENT=$(echo "$TEMPLATE_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('template',''))" 2>/dev/null)
TEMPLATE_SOURCE="内置模板(API)"
if [ -z "$TEMPLATE_CONTENT" ]; then
echo "⚠️ API获取失败,尝试本地模板..."
TEMPLATE_DIR="$HOME/.openclaw/skills/contract-rule-review/templates"
case "$CONTRACT_TYPE" in
"采购合同") TEMPLATE_FILE="$TEMPLATE_DIR/采购合同审阅模板.md" ;;
"服务合同") TEMPLATE_FILE="$TEMPLATE_DIR/服务合同审阅模板.md" ;;
*) TEMPLATE_FILE="$TEMPLATE_DIR/通用合同审阅模板.md" ;;
esac
if [ -f "$TEMPLATE_FILE" ]; then
TEMPLATE_CONTENT=$(cat "$TEMPLATE_FILE")
TEMPLATE_SOURCE=$(basename "$TEMPLATE_FILE")
else
TEMPLATE_CONTENT="请按照通用合同审阅标准进行审阅"
TEMPLATE_SOURCE="默认模板"
fi
fi
echo "✅ 使用模板: $TEMPLATE_SOURCE"
# ========== Step 2: 创建目录结构(4级) ==========
echo ""
echo "📁 创建目录结构..."
# 辅助函数:检查目录是否已存在,返回完整GNS
# 关键:folder_sub_objects 用真实ID查询才能返回子目录,用中文路径名查会返回空
check_dir_exists() {
local parent_gns="$1"
local dir_name="$2"
local result=$(mcporter call anyshare-asmcp.folder_sub_objects \
access_token:"$ACCESS_TOKEN" \
id:"$parent_gns" limit:100 2>&1)
echo "$result" | python3 -c "
import sys,json
data=json.load(sys.stdin)
for d in data.get('dirs',[]):
if d.get('name')=='$dir_name':
print(d.get('id',''))
break
" 2>/dev/null
}
# 1. 主目录 "合同审阅"
# 先用 check_dir_exists 查;查不到则创建,创建后必须用返回值中的真实 id
MAIN_DIR_GNS=$(check_dir_exists "$PERSONAL_DOC_LIB_GNS" "合同审阅")
if [ -z "$MAIN_DIR_GNS" ]; then
MAIN_DIR_RESULT=$(mcporter call anyshare-asmcp.dir_create \
access_token:"$ACCESS_TOKEN" \
docid:"$PERSONAL_DOC_LIB_GNS" \
name:"合同审阅" 2>&1)
MAIN_DIR_GNS=$(echo "$MAIN_DIR_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('docid',''))" 2>/dev/null)
if [ -z "$MAIN_DIR_GNS" ]; then
echo "⚠️ 主目录创建失败,无法继续"
exit 1
fi
fi
# 2. 日期目录
DATE_DIR_GNS=$(check_dir_exists "$MAIN_DIR_GNS" "$TODAY_DIR")
if [ -z "$DATE_DIR_GNS" ]; then
DATE_DIR_RESULT=$(mcporter call anyshare-asmcp.dir_create \
access_token:"$ACCESS_TOKEN" \
docid:"$MAIN_DIR_GNS" \
name:"$TODAY_DIR" 2>&1)
DATE_DIR_GNS=$(echo "$DATE_DIR_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('docid',''))" 2>/dev/null)
if [ -z "$DATE_DIR_GNS" ]; then
echo "⚠️ 日期目录创建失败,无法继续"
exit 1
fi
fi
# 3. 合同名称目录
CONTRACT_DIR_NAME="${FILE_NAME%.docx}"
CONTRACT_DIR_GNS=$(check_dir_exists "$DATE_DIR_GNS" "$CONTRACT_DIR_NAME")
if [ -z "$CONTRACT_DIR_GNS" ]; then
CONTRACT_DIR_RESULT=$(mcporter call anyshare-asmcp.dir_create \
access_token:"$ACCESS_TOKEN" \
docid:"$DATE_DIR_GNS" \
name:"$CONTRACT_DIR_NAME" 2>&1)
CONTRACT_DIR_GNS=$(echo "$CONTRACT_DIR_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('docid',''))" 2>/dev/null)
if [ -z "$CONTRACT_DIR_GNS" ]; then
echo "⚠️ 合同目录创建失败,无法继续"
exit 1
fi
fi
# 4. 「合同原文」子目录
ORIG_DIR_GNS=$(check_dir_exists "$CONTRACT_DIR_GNS" "合同原文")
if [ -z "$ORIG_DIR_GNS" ]; then
ORIG_DIR_RESULT=$(mcporter call anyshare-asmcp.dir_create \
access_token:"$ACCESS_TOKEN" \
docid:"$CONTRACT_DIR_GNS" \
name:"合同原文" 2>&1)
ORIG_DIR_GNS=$(echo "$ORIG_DIR_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('docid',''))" 2>/dev/null)
if [ -z "$ORIG_DIR_GNS" ]; then
echo "⚠️ 合同原文目录创建失败,无法继续"
exit 1
fi
fi
echo "✅ 目录: 合同审阅/$TODAY_DIR/$CONTRACT_DIR_NAME/合同原文/"
echo " 合同目录: $CONTRACT_DIR_GNS"
echo " 合同原文目录: $ORIG_DIR_GNS"
# ========== Step 3: 上传合同原文到「合同原文」子目录 ==========
echo ""
echo "📤 上传合同原文到「合同原文/」..."
UPLOAD=$(mcporter call anyshare-asmcp.file_osbeginupload \
access_token:"$ACCESS_TOKEN" \
docid:"$ORIG_DIR_GNS" \
name:"$FILE_NAME" \
length:$FILE_SIZE 2>&1)
URL=$(echo "$UPLOAD" | python3 -c "import sys,json; print(json.load(sys.stdin)['authrequest'][1])")
AUTH=$(echo "$UPLOAD" | python3 -c "import sys,json; print(json.load(sys.stdin)['authrequest'][2])")
DATE=$(echo "$UPLOAD" | python3 -c "import sys,json; print(json.load(sys.stdin)['authrequest'][4])")
DOCID=$(echo "$UPLOAD" | python3 -c "import sys,json; print(json.load(sys.stdin)['docid'])")
REV=$(echo "$UPLOAD" | python3 -c "import sys,json; print(json.load(sys.stdin)['rev'])")
cat "$LOCAL_FILE" | curl -s -X PUT \
-H "$AUTH" \
-H "Content-Type: application/octet-stream" \
-H "$DATE" \
-T - \
"$URL" > /dev/null 2>&1
END_RESULT=$(mcporter call anyshare-asmcp.file_osendupload \
access_token:"$ACCESS_TOKEN" \
docid:"$DOCID" \
rev:"$REV" 2>&1)
END_CODE=$(echo "$END_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('code',0))" 2>/dev/null)
SHORT_ID=$(echo "$DOCID" | awk -F'/' '{print $NF}')
if [ "$END_CODE" = "0" ] || [ -z "$END_CODE" ]; then
echo "✅ 合同原文上传完成: $SHORT_ID"
UPLOAD_SUCCESS=true
else
echo "⚠️ 合同原文上传失败: code=$END_CODE"
echo " 响应: $END_RESULT"
SHORT_ID=""
UPLOAD_SUCCESS=false
fi
# ========== Step 4: 上传至临时区域(仅上传成功时) ==========
if [ "$UPLOAD_SUCCESS" = true ]; then
echo ""
echo "📤 上传至临时区域..."
TEMPORARY_AREA_RESULT=$(curl -s -X POST \
"https://anyshare.aishu.cn/api/intelli-search/v1/temporary-area" \
-H "authorization: $ACCESS_TOKEN" \
-H "content-type: application/json" \
-d "{\"source\":[{\"id\":\"$SHORT_ID\",\"type\":\"doc\"}],\"bot_id\":\"smart_assistant\"}" 2>&1)
TEMPORARY_AREA_CODE=$(echo "$TEMPORARY_AREA_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('code',0))" 2>/dev/null)
if [ "$TEMPORARY_AREA_CODE" = "0" ] || [ -z "$TEMPORARY_AREA_CODE" ]; then
echo "✅ 临时区域上传成功"
else
echo "⚠️ 临时区域上传返回: $TEMPORARY_AREA_RESULT"
fi
else
echo "⚠️ 跳过临时区域(上传失败)"
fi
# ========== Step 5: 等待索引建立(仅上传成功时) ==========
if [ "$UPLOAD_SUCCESS" = true ]; then
echo ""
echo "⏳ 等待索引建立..."
# 注意:file_osendupload 返回值中不包含 details 字段
# 因此 FILE_DETAILS 直接使用文件短 ID 构建,details 留空
# 关键:index-check 不带 retry=false,让索引服务重新处理文件
FILE_DETAILS="[{\"id\":\"$SHORT_ID\",\"type\":\"doc\",\"details\":{}}]"
echo "✅ FILE_DETAILS 已构建(details 留空,由 temporary-area 触发索引)"
MAX_WAIT=600
INTERVAL=5
ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
INDEX_CHECK_RESULT=$(curl -s -X POST \
"https://anyshare.aishu.cn/api/intelli-search/v1/index-check?target_index=pageindex" \
-H "authorization: $ACCESS_TOKEN" \
-H "content-type: application/json" \
-d "$FILE_DETAILS" 2>&1)
PROCESS=$(echo "$INDEX_CHECK_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('process',0))" 2>/dev/null)
if [ "$PROCESS" = "100" ]; then
echo "✅ 索引建立完成(${ELAPSED}秒)"
break
else
echo " 索引进度: ${PROCESS:-0}%(${ELAPSED}秒)..."
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
fi
done
[ $ELAPSED -ge $MAX_WAIT ] && echo "⚠️ 索引超时,继续..."
# ========== Step 6: 调用 smart_assistant 审阅(仅上传成功时) ==========
if [ "$UPLOAD_SUCCESS" = true ]; then
echo ""
echo "🔍 执行规则审阅..."
REPORT_CONTENT=$(mcporter call anyshare-asmcp.smart_assistant \
--timeout 180000 \
access_token:"$ACCESS_TOKEN" \
query:"帮我审核这份合同,按照以下模板审阅:$TEMPLATE_CONTENT" \
skill_name:"__规则审阅__1" \
source_ranges:"[{\"id\":\"$SHORT_ID\",\"type\":\"doc\"}]" 2>&1)
REPORT_CONTENT=$(echo "$REPORT_CONTENT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('completion_answer',''))" 2>/dev/null)
if [ -z "$REPORT_CONTENT" ]; then
echo "⚠️ 审阅返回内容为空"
REPORT_CONTENT="审阅失败,未获取到审阅内容"
else
echo "✅ 审阅完成"
fi
else
echo "⚠️ 跳过审阅(合同原文上传失败)"
REPORT_CONTENT="合同原文上传失败,无法进行审阅。"
fi
# ========== Step 7: 保存审阅报告到合同目录 ==========
echo ""
echo "📄 保存审阅报告..."
REPORT_FILE="审阅报告.md"
REPORT_SIZE=${#REPORT_CONTENT}
REPORT_BEGIN=$(mcporter call anyshare-asmcp.file_osbeginupload \
access_token:"$ACCESS_TOKEN" \
docid:"$CONTRACT_DIR_GNS" \
name:"$REPORT_FILE" \
length:$REPORT_SIZE 2>&1)
AUTH_H=$(echo "$REPORT_BEGIN" | python3 -c "import sys,json; print(json.load(sys.stdin)['authrequest'][2])")
DATE_H=$(echo "$REPORT_BEGIN" | python3 -c "import sys,json; print(json.load(sys.stdin)['authrequest'][4])")
UPLOAD_URL=$(echo "$REPORT_BEGIN" | python3 -c "import sys,json; print(json.load(sys.stdin)['authrequest'][1])")
R_DOCID=$(echo "$REPORT_BEGIN" | python3 -c "import sys,json; print(json.load(sys.stdin)['docid'])")
R_REV=$(echo "$REPORT_BEGIN" | python3 -c "import sys,json; print(json.load(sys.stdin)['rev'])")
# ⚠️ 必须使用原始 Content-Type (application/octet-stream),不得覆盖
echo -n "$REPORT_CONTENT" | curl -s -X PUT \
-H "$AUTH_H" \
-H "Content-Type: application/octet-stream" \
-H "$DATE_H" \
-T - \
"$UPLOAD_URL" > /dev/null 2>&1
mcporter call anyshare-asmcp.file_osendupload \
access_token:"$ACCESS_TOKEN" \
docid:"$R_DOCID" \
rev:"$R_REV" > /dev/null 2>&1
echo "✅ 审阅报告已保存"
# ========== Step 8: 生成分享链接 ==========
echo ""
echo "🔗 生成分享链接..."
SHARE_RESULT=$(mcporter call anyshare-asmcp.file_sharedlink_realname_create \
access_token:"$ACCESS_TOKEN" \
item:"{\"id\":\"$R_DOCID\",\"type\":\"file\"}" 2>&1)
SHARE_URL=$(echo "$SHARE_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('link_url','生成失败'))" 2>/dev/null)
# ========== 输出结果 ==========
if [ "$UPLOAD_SUCCESS" = true ]; then
ORIG_STATUS="✅ 合同原文/$FILE_NAME"
else
ORIG_STATUS="⚠️ 合同原文(上传失败)"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ 审阅完成"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📄 $ORIG_STATUS"
echo "📄 审阅报告:$REPORT_FILE"
echo "📍 保存位置:"
echo " 个人文档库/合同审阅/$TODAY_DIR/$CONTRACT_DIR_NAME/"
if [ "$UPLOAD_SUCCESS" = true ]; then
echo " ├─ 合同原文/$FILE_NAME"
echo " └─ 审阅报告.md"
else
echo " └─ 审阅报告.md"
fi
echo "🔗 分享链接:$SHARE_URL"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
MCP Tool 完整列表
| Tool | 用途 | 调用阶段 |
|---|---|---|
dir_create | 创建目录(4级) | Step 2 |
folder_sub_objects | 检查目录是否存在 | Step 2 |
file_osbeginupload | 获取上传凭证 | Step 3, Step 7 |
file_osendupload | 确认上传完成 | Step 3, Step 7 |
smart_assistant | 规则审阅 | Step 6 |
file_sharedlink_realname_create | 创建分享链接 | Step 8 |
依赖
- 模板目录:
~/.openclaw/skills/contract-rule-review/templates/ - MCP:
anyshare-asmcp - Tools:
file_osbeginupload,file_osendupload,dir_create,folder_sub_objects,smart_assistant,file_sharedlink_realname_create
注意
- 需预先配置
PERSONAL_DOC_LIB_GNS(个人文档库GNS路径)和有效的ACCESS_TOKEN - 目录结构严格遵守:合同原文必须上传到「合同原文/」子目录,审阅报告直接放在合同名称目录下
- 上传 curl 命令:
Content-Type必须使用原始的application/octet-stream,不得覆盖,否则签名失败 - 目录复用逻辑:使用
folder_sub_objects检查已存在目录,避免重复创建 - 分享链接为实名链接
版本:2.0.0