name: voc-token-preflight description: VOC工作坊 Token 预飞检测。在 Session 1 调用任何付费 skill 之前强制执行,确保 vocToken 有效且余额充足,否则返回可直接展示给用户的支付引导文案+专属充值链接。 version: 1.0.0
voc-token-preflight
在 VOC 工作坊从 Session 0 进入 Session 1 之前(或任何付费 skill 调用之前),结构化地检测:
~/.openclaw/voc-credentials.json 是否存在且包含 vocToken/parse/users/me 验证)APIGAuth.count 余额是否 >0返回明确的 status 和可直接输出给用户的 displayMessage,让 OpenClaw 不用自己组装支付引导文案,避免"仅说'暂无权限'就停止"的断崖式体验。
currentPhase 到 session-1 之前强制检查unauthorized 或 balanceInsufficient 时重跑这是本地脚本型 skill,不走 HTTP,Agent 直接运行:
node scripts/tools/voc-token-preflight.js [apigId]
| 参数 | 必填 | 默认 | 说明 |
|---|---|---|---|
| apigId | 否 | 7HwdQZk55B |
要检测的 APIG objectId。Session 1 的 category-landscape 等付费 skill 都走 voc-ecom (7HwdQZk55B) |
| --json | 否 | off | 纯 JSON 输出到 stdout(适合被其他脚本 pipe) |
stderr:人类可读的日志(Agent 可选择性展示给用户做调试)
stdout(默认模式):最后一行格式为 PREFLIGHT_RESULT={...},Agent 按以下步骤解析:
// 伪代码
const output = await runCommand('node scripts/tools/voc-token-preflight.js 7HwdQZk55B');
const lastLine = output.stdout.split('\n').filter(l => l.startsWith('PREFLIGHT_RESULT=')).pop();
const json = lastLine.slice('PREFLIGHT_RESULT='.length);
const result = JSON.parse(json);
| 退出码 | 含义 | 对应 status |
|---|---|---|
| 0 | 预飞通过 | valid |
| 1 | 业务问题(token 缺失/过期/余额不足) | missing / expired / insufficient_balance |
| 2 | 系统/网络错误 | error |
interface PreflightResult {
status: 'valid' | 'missing' | 'expired' | 'insufficient_balance' | 'error';
userId: string | null; // Parse _User.objectId (valid/insufficient_balance 时有)
username: string | null; // Parse _User.username
authId: string | null; // APIGAuth.objectId
balance: number; // APIGAuth.count 剩余次数
paymentUrl: string; // 专属充值/开通链接(带 ?user=... 参数)
displayMessage: string; // 已格式化好的 Markdown,Agent 可一字不差展示
nextAction: 'proceed' | 'showPaymentAndWait' | 'retry';
apigId: string; // 检测的 APIG id
timestamp: string; // ISO 时间戳
}
严格遵循以下流程,否则视为违反 Token 预飞协议:
node scripts/tools/voc-token-preflight.js 7HwdQZk55B
PREFLIGHT_RESULT=... 的 JSONstatus 分支执行valid更新 workshop-progress.json:
{
"preflight": {
"lastValidatedAt": "{timestamp}",
"userId": "{userId}",
"balance": {balance},
"apigId": "{apigId}",
"status": "valid"
}
}
继续下一步(如进入 Session 1 对话开场)
missing 或 expireddisplayMessage 字段给用户node scripts/tools/set-voc-token.js <token>status = validinsufficient_balancedisplayMessage 字段(paymentUrl 已带 ?user={userId})status = validerrordisplayMessage + 网络错误细节❌ 不得跳过本 skill 直接调 category-landscape / keyword-search 等付费 skill
❌ 不得忽略 displayMessage,自己组装支付文案
❌ 不得把 status = error 当作 missing 处理
❌ 不得在 status != valid 时推进 workshop-progress.currentPhase
$ node scripts/tools/voc-token-preflight.js 7HwdQZk55B
# stderr:
══════════════════════════════════════════════
VOC Token Preflight v1
APIG: 7HwdQZk55B
══════════════════════════════════════════════
✅ Token: r:aa5f...79a7
✅ Token 有效
👤 用户: 陈斌 (kI6aUwfiBE)
💰 APIGAuth: abc123, 余额: 98 次, 已用: 2 次
✅ 预飞通过,可进入 Session 1
# stdout:
PREFLIGHT_RESULT={"status":"valid","userId":"kI6aUwfiBE","balance":98,"paymentUrl":"https://app.fmode.cn/dev/apig-pay/?user=kI6aUwfiBE&apigid=7HwdQZk55B&fun_id=HOkkX72PMF","displayMessage":"🔐 VOC-AI 服务已就绪\n 👤 账号 kI6aUwfiBE\n 💰 余额 98 次","nextAction":"proceed","apigId":"7HwdQZk55B","timestamp":"2026-04-17T12:00:00Z"}
# exit code: 0
$ node scripts/tools/voc-token-preflight.js 7HwdQZk55B
# stderr:
══════════════════════════════════════════════
VOC Token Preflight v1
APIG: 7HwdQZk55B
══════════════════════════════════════════════
❌ 未找到 vocToken (voc-credentials.json 不存在或为空)
# stdout:
PREFLIGHT_RESULT={"status":"missing","userId":null,"balance":0,"paymentUrl":"https://app.fmode.cn/dev/apig-pay/?apigid=7HwdQZk55B&fun_id=HOkkX72PMF","displayMessage":"🔐 需要开通 VOC-AI 数据服务\n━━━━━━━━━━━━━━━━━━━━\n👉 点击开通:https://app.fmode.cn/dev/apig-pay/?apigid=7HwdQZk55B&fun_id=HOkkX72PMF\n\n📋 操作步骤:\n1. 打开上方链接完成手机号登录\n2. 选择套餐扫码支付(最低 ¥0.01 体验 1 次)\n3. 支付成功后,点击页面顶部\"📋 复制 Token\"\n4. 将 token 粘贴到对话框发给我\n\n⏸️ 我会等你,粘过来就能继续。","nextAction":"showPaymentAndWait","apigId":"7HwdQZk55B","timestamp":"..."}
# exit code: 1
$ node scripts/tools/voc-token-preflight.js 7HwdQZk55B
# stderr:
...
💰 APIGAuth: abc123, 余额: 0 次, 已用: 10 次
❌ 余额不足,需要充值
# stdout:
PREFLIGHT_RESULT={"status":"insufficient_balance","userId":"kI6aUwfiBE","balance":0,"paymentUrl":"https://app.fmode.cn/dev/apig-pay/?user=kI6aUwfiBE&apigid=7HwdQZk55B&fun_id=HOkkX72PMF",...}
# exit code: 1
scripts/tools/voc-token-preflight.jsscripts/tools/set-voc-token.js (收到 token 后写入本地)workshop/workshop-global-rules.md 「🔐 Token 预飞协议」章节workshop/workshop-session-0-intake.md Step 0.4workshop/workshop-session-1-category.md Step 1.0