mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
DingTalk can receive interactive card callbacks, but the card UI still depends on a published template, so the adapter now supports a template-backed card path with text commands as the reliable fallback. Telegram and WeChat use the same allow-once, allow-always, and deny semantics so manual authorization behaves the same across platforms. Constraint: DingTalk button rendering requires an operator-provided interactive card template id Rejected: Treat the existing AI streaming card template as a permission card | it cannot guarantee visible action buttons Confidence: high Scope-risk: moderate Directive: Do not remove the text approval fallback unless DingTalk card template provisioning is guaranteed Tested: bun test adapters/common/__tests__/permission.test.ts adapters/dingtalk/__tests__/permission-card.test.ts adapters/telegram/__tests__/telegram.test.ts adapters/common/__tests__/config.test.ts src/server/__tests__/adapters.test.ts Tested: bunx tsc -p adapters/tsconfig.json --noEmit Tested: bun run check:adapters Tested: cd desktop && bun run build Tested: bun run check:server Tested: bun run check:docs Tested: git diff --check Not-tested: bun run quality:pr is blocked by existing CLI core approval policy; see artifacts/quality-runs/2026-05-03T13-19-56-232Z/report.md
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
export type PermissionDecision = {
|
||
requestId: string
|
||
allowed: boolean
|
||
rule?: 'always'
|
||
}
|
||
|
||
export function parsePermissionCommand(text: string): PermissionDecision | null {
|
||
const match = text.trim().match(/^\/(allow|always|allow-always|deny)\s+(\S+)/i)
|
||
if (!match) return null
|
||
|
||
const action = match[1]!.toLowerCase()
|
||
const requestId = match[2]!
|
||
if (action === 'deny') return { requestId, allowed: false }
|
||
if (action === 'always' || action === 'allow-always') return { requestId, allowed: true, rule: 'always' }
|
||
return { requestId, allowed: true }
|
||
}
|
||
|
||
export function parsePermitCallbackData(data: string): PermissionDecision | null {
|
||
const parts = data.split(':')
|
||
if (parts.length !== 3 || parts[0] !== 'permit' || !parts[1]) return null
|
||
|
||
switch (parts[2]) {
|
||
case 'yes':
|
||
return { requestId: parts[1], allowed: true }
|
||
case 'always':
|
||
return { requestId: parts[1], allowed: true, rule: 'always' }
|
||
case 'no':
|
||
return { requestId: parts[1], allowed: false }
|
||
default:
|
||
return null
|
||
}
|
||
}
|
||
|
||
export function formatPermissionInstructions(requestId: string): string {
|
||
return [
|
||
`回复 /allow ${requestId} 允许一次`,
|
||
`/always ${requestId} 永久允许`,
|
||
`/deny ${requestId} 拒绝。`,
|
||
].join(',')
|
||
}
|
||
|
||
export function formatPermissionDecisionStatus(decision: Pick<PermissionDecision, 'allowed' | 'rule'>): string {
|
||
if (!decision.allowed) return '❌ 已拒绝'
|
||
return decision.rule === 'always' ? '♾️ 已永久允许' : '✅ 已允许'
|
||
}
|