分析轮次 7-12 合并 — 扩增版¶
分析周期: 2026-05-16 — 2026-05-24 覆盖: ACP 事件全表 → 授权矩阵 → 百智云 OAuth → 多轮对话 → 订阅计费 → 号池管理 代理源码:
proxy/src/account-pool.ts(298L),admin-login.ts(416L),conversation-manager.ts(368L)
轮次 7: ACP 事件全表确认¶
目标: 确认 ACP 事件的完整类型和字段结构
ACP 事件类型全表¶
{
"agent_message_chunk": {
"type": "agent_message_chunk",
"text": "Agent 输出的文本内容",
"content": "与 text 相同或补充字段",
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0
},
"agent_thought_chunk": {
"type": "agent_thought_chunk",
"text": "Agent 的推理过程文本",
"content": "推理补充内容"
},
"tool_call": {
"type": "tool_call",
"tool_name": "工具名称",
"tool_input": "工具输入参数"
},
"tool_call_update": {
"type": "tool_call_update",
"tool_name": "工具名称",
"tool_input": "增量参数",
"delta": "增量更新数据",
"status": "running / completed / done"
},
"usage_update": {
"type": "usage_update",
"input_tokens": 150,
"output_tokens": 200,
"total_tokens": 350
},
"plan": {
"type": "plan",
"steps": ["步骤1", "步骤2", "..."]
},
"available_commands_update": {
"type": "available_commands_update",
"commands": ["命令1", "命令2"]
}
}
代理层 ACP 事件处理源码¶
// 摘自 proxy/src/task-runner.ts — ACP 事件处理
private handleACPEvent(
acp: ACPSessionUpdate,
chatId: string,
now: number,
onChunk: (chunk: OpenAIChatCompletionChunk) => void,
usage: { input_tokens: number; output_tokens: number; total_tokens: number }
): void {
switch (acp.type) {
case "agent_message_chunk": {
const text = acp.text || acp.content || ""
if (text) {
onChunk({
id: chatId,
object: "chat.completion.chunk",
created: now,
model: "monkeycode",
choices: [{ index: 0, delta: { content: text }, finish_reason: null }],
})
}
break
}
case "agent_thought_chunk": {
const text = acp.text || acp.content || ""
if (text) {
// Agent 推理内容添加 [Thinking] 前缀
onChunk({
id: chatId, object: "chat.completion.chunk",
created: now, model: "monkeycode",
choices: [{ index: 0, delta: { content: `[Thinking] ${text}` }, finish_reason: null }],
})
}
break
}
case "usage_update":
// 累积 token 用量
if (acp.input_tokens) usage.input_tokens = acp.input_tokens
if (acp.output_tokens) usage.output_tokens = acp.output_tokens
if (acp.total_tokens) usage.total_tokens = acp.total_tokens
break
case "tool_call": {
const toolName = acp.tool_name || "unknown"
const toolInput = acp.tool_input || ""
onChunk({
id: chatId, object: "chat.completion.chunk",
created: now, model: "monkeycode",
choices: [{ index: 0, delta: { content: `[Tool: ${toolName}] ${toolInput}` }, finish_reason: null }],
})
break
}
case "tool_call_update": {
// 记录工具调用更新(调试用)
const updateArgs = String(acp.tool_input || acp.delta || "")
const status = String(acp.status || "")
console.log(`[TaskRunner] tool_call_update: status=${status}, args=${updateArgs.slice(0, 100)}`)
break
}
case "plan": {
const planData = acp.steps || acp
console.log(`[TaskRunner] plan:`, JSON.stringify(planData).slice(0, 200))
break
}
case "available_commands_update": {
const commandsData = acp.commands || acp
console.log(`[TaskRunner] available_commands:`, JSON.stringify(commandsData).slice(0, 200))
break
}
}
}
轮次 8: 授权矩阵 4 级确认¶
目标: 确认 MonkeyCode 的四级权限体系
授权层级¶
| 层级 | 访问范围 | Cookie | API 前缀 |
|---|---|---|---|
| public | 公开端点 | 无需 Cookie | /api/v1/public/ |
| user | 用户级别 | monkeycode_ai_session |
/api/v1/users/ |
| team | 团队级别 | monkeycode_ai_team_session |
/api/v1/teams/ |
| admin | 管理员 | 需要 is_admin=true | /api/v1/admin/ |
代理层权限检查¶
// 摘自 proxy/src/auth.ts — 登录模式切换
export type LoginMode = "user" | "team"
// 根据模式选择不同的 Cookie 名和端点
async login(): Promise<void> {
if (this.loginMode === "team") {
await this.loginTeam() // 团队登录
} else {
await this.loginUser() // 用户登录
}
}
// 用户状态检查
async checkStatus(): Promise<boolean> {
const url = this.loginMode === "team"
? `${MONKEYCODE_BASE_URL}/api/v1/teams/users/status`
: `${MONKEYCODE_BASE_URL}/api/v1/users/status`
const response = await fetch(url, {
headers: mkHeaders({
Cookie: `${this.getSessionCookieName()}=${this.getSessionCookieSync()}`,
}),
})
return response.ok
}
轮次 9: 百智云 OAuth 流程逆向¶
目标: 理解百智云 OAuth 的完整 6 步流程
OAuth 6 步流程¶
Step 1: GET /api/v1/users/login → 302 Redirect to baizhi.cloud OAuth
获取 state, client_id, redirect_uri, scope
Step 2: POST SCaptcha → 获取验证码 token
(欠费状态 token 仍可获取,challenge 为空)
Step 3: POST baizhi.cloud/api/v1/user/phone_code → 发送短信验证码
需要: phone + captcha_token
Step 4: POST baizhi.cloud/api/v1/user/login/phone → 手机号登录
需要: phone + sms_code
返回: baizhi.cloud session cookies
Step 5: GET baizhi.cloud/api/v1/oauth/authorize? → 获取 OAuth code
需要: client_id + redirect_uri + scope + state + baizhi cookies
返回: callback URL with code
Step 6: 访问 callback URL → 获取 monkeycode_ai_session Cookie
OAuth code 交换 → monkeycode Session Cookie
代理层 OAuth 实现¶
// 摘自 proxy/src/admin-login.ts — Step 1: 获取 OAuth 参数
export async function startOAuthLogin(): Promise<{
oauthUrl: string
state: string
clientId: string
redirectUri: string
scope: string
}> {
const resp = await fetch(`${MONKEYCODE_BASE_URL}/api/v1/users/login`, {
headers: mkHeaders(),
redirect: "manual",
})
if (resp.status !== 302) {
throw new Error(`Expected 302 redirect, got ${resp.status}`)
}
const location = resp.headers.get("Location") || ""
const url = new URL(location)
return {
oauthUrl: location,
state: url.searchParams.get("state") || "",
clientId: url.searchParams.get("client_id") || "",
redirectUri: url.searchParams.get("redirect_uri") || "",
scope: url.searchParams.get("scope") || "",
}
}
SCaptcha Token 获取¶
// 摘自 proxy/src/admin-login.ts — Step 2: SCaptcha 绕过
export async function getSCaptchaToken(): Promise<string> {
// SCaptcha 服务 TLS 证书过期,需绕过证书验证
const originalTlsSetting = process.env.NODE_TLS_REJECT_UNAUTHORIZED
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
try {
const resp = await fetch(`${SCAPTCHA_API}/v1/api/challenge`, {
method: "POST",
headers: scHeaders(),
body: JSON.stringify({ business_id: SCAPTCHA_BUSINESS_ID }),
})
const data = await resp.json() as any
return data.data?.token || ""
} finally {
// 恢复 TLS 设置
if (originalTlsSetting === undefined) {
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED
} else {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = originalTlsSetting
}
}
}
轮次 10: 多轮对话设计(mode=attach)¶
目标: 设计基于 mode=attach 的多轮对话复用机制
Conversation 数据结构¶
// 摘自 proxy/src/conversation-manager.ts — Conversation 接口
export interface Conversation {
id: string // 格式: conv-{timestamp}-{random}
taskId: string // 关联的任务 ID
model: MonkeyCodeModel // 模型
auth: AuthManager // 认证
ws: WebSocket | null // 复用的 WS 连接
messages: OpenAIMessage[] // 历史消息
lastUsedAt: number // 最后使用时间
createdAt: number // 创建时间
onChunk: ((chunk: OpenAIChatCompletionChunk) => void) | null // 回调
resolvePromise: (() => void) | null // Promise 解析
rejectPromise: ((err: Error) => void) | null // Promise 拒绝
}
mode=attach 连接复用¶
// 摘自 proxy/src/conversation-manager.ts — connectToTask 方法
async connectToTask(
conversation: Conversation,
onChunk: (chunk: OpenAIChatCompletionChunk) => void
): Promise<void> {
// mode=attach 复用已有任务的 WebSocket
const wsUrl = `${wsBaseUrl}/api/v1/users/tasks/stream?id=${conversation.taskId}&mode=attach`
const ws = new WebSocket(wsUrl, {
headers: wsHeaders("monkeycode-ai.com",
`${auth.getSessionCookieName()}=${auth.getSessionCookieSync()}`),
})
// 与 mode=new 完全相同的 auto-approve + 心跳逻辑
ws.on("open", () => {
ws.send(JSON.stringify({ type: "auto-approve" }))
})
}
mode=new vs mode=attach 对比¶
| 特性 | mode=new | mode=attach |
|---|---|---|
| URL 参数 | mode=new |
mode=attach |
| 创建新 VM | ✅ 是 | ❌ 复用 |
| 适用 SDK | OpenAI Chat/Responses | Conversations API |
| 代理类 | TaskRunner | ConversationManager |
| 超时 | 1h (TASK_TIMEOUT_MS) | 30min (conversationTimeoutMs) |
| 清理 | 任务结束自动销毁 | 超时自动清理 |
轮次 11: 订阅计费分析¶
目标: 理解 MonkeyCode 的订阅和计费系统
订阅响应结构体¶
// SubscriptionResp 响应格式
interface SubscriptionResp {
plan: string // "basic" | "pro" | "ultra"
source: string // "stripe" | "open_source"
expires_at: string // 过期时间 ISO 格式
auto_renew: boolean // 是否自动续费
is_free: boolean // 是否免费
}
// 订阅级别对应的并发限制
const SUBSCRIPTION_LIMITS = {
basic: { maxConcurrency: 1, maxModelsPerType: 3 },
pro: { maxConcurrency: 3, maxModelsPerType: 10 },
ultra: { maxConcurrency: 10, maxModelsPerType: 50 },
}
// 开源版固定为 pro(可绕过)
const OPEN_SOURCE_SUBSCRIPTION = {
plan: "pro",
source: "open_source",
is_free: true,
}
号池绕过订阅限制¶
// 摘自 proxy/src/account-pool.ts — 号池多账号绕过
// 每个账号独立的订阅限制
// 号池通过多账号轮流使用绕过单账号配额限制
class AccountPool {
// HTTP 请求: 共享模式,多个请求分发到不同账号
acquireHttp(): AuthManager | null {
const candidates = this.accounts
.filter((a) => a.status === "ACTIVE" && !a.lockedByWs)
.sort((a, b) => a.lastUsedAt - b.lastUsedAt)
// ... Round-Robin 分配
}
// WebSocket: 独占模式,一个账号绑定一个 WS 流直到结束
acquireWs(): AuthManager | null {
const candidates = this.accounts
.filter((a) => a.status === "ACTIVE" && !a.lockedByWs)
.sort((a, b) => a.lastUsedAt - b.lastUsedAt)
// 取最久未用的 ACTIVE 账号锁定
}
}
轮次 12: 号池管理协议确认¶
目标: 确认 AccountPool 的完整状态机和错误处理体系
账号状态机¶
┌──────────┐ 登录成功 ┌──────────┐
│ CREATED │──────────────▶│ ACTIVE │
│ (创建) │ │ (活跃) │
└──────────┘ └──────────┘
│
┌───────────┴───────────┐
│ │
会话过期/状态检查失败 3次登录失败/
│ 密码错误/账号被封
▼ ▼
┌──────────┐ ┌──────────┐
│ EXPIRED │ │ INVALID │
│ (过期) │ │ (无效) │
└──────────┘ └──────────┘
│
自动重新登录
▼
┌──────────┐
│ ACTIVE │
│ (重新活跃)│
└──────────┘
错误码处理策略¶
// 摘自 proxy/src/account-pool.ts — 错误码分发
handleError(auth: AuthManager, errorCode: number): boolean {
const entry = this.findByAuth(auth)
if (!entry) return false
switch (errorCode) {
case 40100: // 会话无效 → 重登录
entry.status = "EXPIRED"
this.loginAccount(entry).catch(() => {})
return true // 可切换账号重试
case 40300: // 权限不足 → 降级
console.warn(`[AccountPool] ${entry.email}: permission denied, degrading`)
return false
case 40002: // 密码错误
case 40003: // 账号被封
case 40004: // 账号未激活
entry.status = "INVALID"
console.error(`[AccountPool] ${entry.email}: marked INVALID (code ${errorCode})`)
return false
case 50000: // 服务端错误 → 可重试
return true
default:
return false
}
}
健康检查源码¶
// 摘自 proxy/src/account-pool.ts — 定时健康检查
private async healthCheck(): Promise<void> {
for (const entry of this.accounts) {
if (entry.status !== "ACTIVE") continue
// 清理僵尸 WS 锁
if (entry.lockedByWs && entry.lockedAt &&
Date.now() - entry.lockedAt > WS_LOCK_MAX_MS) {
entry.lockedByWs = false
entry.lockedAt = null
}
// 检查 Cookie 年龄(30 天硬限制)
if (entry.cookieSetAt &&
Date.now() - entry.cookieSetAt > SESSION_MAX_AGE_MS) {
await this.loginAccount(entry)
continue
}
// 调用 /users/status 检查有效
try {
const ok = await entry.auth.checkStatus()
if (!ok) {
await this.loginAccount(entry)
}
} catch {
entry.status = "EXPIRED"
}
}
}
本阶段总结¶
| 轮次 | 关键产出 | 后续影响 |
|---|---|---|
| 7 | ACP 事件全表确认 (7 种→9 种) | 完整解析 Agent 实时输出 |
| 8 | 授权矩阵 4 级 | 实现 API 权限检查 |
| 9 | 百智云 OAuth 6 步流程 | 实现纯 HTTP OAuth 自动化 |
| 10 | mode=attach 多轮对话设计 | 实现 ConversationManager |
| 11 | 订阅计费 SubscriptionResp | 号池绕过限制 |
| 12 | 号池状态机 + 5 种错误码 | 完整错误隔离体系 |