feat: 简化微信渠道账号连接

This commit is contained in:
2026-09-14 12:05:16 +08:00
parent a721b8fb7e
commit 21e707d9c4
21 changed files with 238 additions and 1078 deletions

View File

@@ -15,7 +15,7 @@ const mcpInput = ['name', 'description', 'transport', 'url', 'headers'];
const mcpOutput = ['slug', 'name', 'description', 'transport', 'url', 'enabled', 'has_credentials'];
const child = ['name', 'purpose', 'system_prompt'];
const channelView = ['id', 'address', 'display_name', 'target_agent_address', 'route_revision', 'provider_generation', 'binding_id', 'enabled', 'status', 'worker_online',
'agent_slug', 'published_version', 'desired_state', 'sync_state', 'health', 'last_confirmed_at', 'revision', 'blockers', 'access_mode', 'policy_revision',
'agent_slug', 'published_version', 'desired_state', 'sync_state', 'health', 'last_confirmed_at', 'revision', 'blockers',
'adoption_state', 'managed_agent_slug', 'target_agent_slug', 'target_agent_name', 'target_agent_published_version'];
const channelSession = ['session_id', 'caller_id', 'agent_slug', 'agent_name', 'binding_id', 'provider_generation', 'channel_account_id', 'core_conversation_id', 'access_mode', 'grant_state', 'session_state', 'sequence', 'content_uid', 'thread_id', 'created_at', 'closed_at'];
const channelCaller = ['caller_id', 'binding_id', 'provider_generation', 'channel_account_id', 'agent_slug', 'core_conversation_id', 'access_mode', 'grant_state', 'paired_ws_account_id', 'session_id', 'content_uid', 'thread_id', 'session_state', 'created_at', 'revoked_at'];
@@ -62,15 +62,6 @@ function projectMutation(value: unknown): unknown {
...(source.disconnected === undefined ? {} : { disconnected: source.disconnected === true }),
};
}
function projectPolicy(value: unknown): unknown {
const source = object(value);
return {
...copy(source, ['binding_id', 'channel_account_id', 'access_mode', 'policy_revision']),
revoked_caller_ids: Array.isArray(source.revoked_caller_ids) ? source.revoked_caller_ids.filter((item): item is string => typeof item === 'string').slice(0, 200) : [],
revoked_invitation_ids: Array.isArray(source.revoked_invitation_ids) ? source.revoked_invitation_ids.filter((item): item is string => typeof item === 'string').slice(0, 200) : [],
};
}
function projectPairing(value: unknown): unknown { return copy(value, ['invitation_id', 'binding_id', 'provider_generation', 'agent_slug', 'kind', 'expires_at', 'consumed', 'code']); }
function projectWechat(value: unknown): unknown {
const source = object(value);
const qrcode_url = safeHttps(source.qrcode_url);
@@ -185,9 +176,9 @@ function validateChannelInput(operation: string, args: ObjectRecord): void {
'channelAccountWechatBindVerification', 'channelAccountWechatBindAccount', 'channelAccountWechatUnbind',
'channelAccountOperation',
'createChannelBinding', 'switchChannelBindingAgent', 'enableChannelBinding', 'pauseChannelBinding',
'disconnectChannelBinding', 'channelPolicy', 'createChannelPairing', 'wechatBindStart',
'disconnectChannelBinding', 'wechatBindStart',
'wechatBindStatus', 'wechatBindVerification', 'wechatBindAccount', 'wechatUnbind', 'channelSelfCallers',
'channelCallers', 'revokeChannelCaller', 'channelActivity', 'channelConversations', 'channelConversation',
'channelActivity', 'channelConversations', 'channelConversation',
'channelFiles', 'channelConversationControl', 'channelDelivery', 'retryChannelDeliveryPart', 'channelOperation',
]);
if (channelOperations.has(operation)) {
@@ -206,8 +197,6 @@ function validateChannelInput(operation: string, args: ObjectRecord): void {
&& (typeof args.target_agent_slug !== 'string' || !/^ml-[a-f0-9]{32}$/.test(args.target_agent_slug))) throw new Error('invalid_id');
if (operation === 'routeChannelAccount' && args.target_agent_slug === null && args.enabled !== false) throw new Error('invalid_input');
if (operation === 'switchChannelBindingAgent' && (typeof args.target_agent_slug !== 'string' || !/^ml-[a-f0-9]{32}$/.test(args.target_agent_slug))) throw new Error('invalid_id');
if (operation === 'channelPolicy' && !['self_only', 'invited'].includes(String(args.access_mode))) throw new Error('invalid_input');
if (operation === 'createChannelPairing' && !['self', 'invite'].includes(String(args.kind))) throw new Error('invalid_input');
if (operation === 'wechatBindStart' && typeof args.force !== 'boolean') throw new Error('invalid_input');
if (operation === 'channelAccountWechatBindStart' && typeof args.force !== 'boolean') throw new Error('invalid_input');
if (operation === 'wechatBindVerification' && (typeof args.verify_code !== 'string' || !/^[0-9A-Za-z-]{1,128}$/.test(args.verify_code))) throw new Error('invalid_input');
@@ -302,16 +291,12 @@ const operations: Record<keyof CloudAgentOperations, OperationSpec> = {
enableChannelBinding: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/enable', body: ['operation_id', 'expected_revision'], output: ['channel'], project: projectMutation },
pauseChannelBinding: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/pause', body: ['operation_id', 'expected_revision'], output: ['channel'], project: projectMutation },
disconnectChannelBinding: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/disconnect', body: ['operation_id', 'expected_revision'], output: ['channel', 'wechat', 'disconnected'], project: projectMutation },
channelPolicy: { method: 'PATCH', path: '/agents/:slug/channel-bindings/:channel_account_id', body: ['operation_id', 'access_mode', 'expected_policy_revision'], output: ['binding_id', 'channel_account_id', 'access_mode', 'policy_revision', 'revoked_caller_ids', 'revoked_invitation_ids'], project: projectPolicy },
createChannelPairing: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/pairings', body: ['operation_id', 'kind'], output: ['invitation_id', 'binding_id', 'provider_generation', 'agent_slug', 'kind', 'expires_at', 'consumed', 'code'], project: projectPairing },
wechatBindStart: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/wechat-bind/start', body: ['operation_id', 'force'], output: ['session_key', 'status', 'message', 'qrcode_url'], project: projectWechat },
wechatBindStatus: { method: 'GET', path: '/agents/:slug/channel-bindings/:channel_account_id/wechat-bind/status', query: { session_key: 'session_key' }, output: ['session_key', 'status', 'message', 'qrcode_url'], project: projectWechat },
wechatBindVerification: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/wechat-bind/verification', body: ['operation_id', 'session_key', 'verify_code'], output: ['session_key', 'status', 'message', 'qrcode_url'], project: projectWechat },
wechatBindAccount: { method: 'GET', path: '/agents/:slug/channel-bindings/:channel_account_id/wechat-bind/account', output: ['status', 'message'] },
wechatUnbind: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/wechat-bind/unbind', body: ['operation_id'], output: ['status', 'message'] },
channelSelfCallers: { method: 'GET', path: '/agents/:slug/channel-callers', fixedQuery: { access_mode: 'self_only' }, output: ['items'], project: projectCallers },
channelCallers: { method: 'GET', path: '/agents/:slug/channel-bindings/:channel_account_id/callers', output: ['items'], project: projectCallers },
revokeChannelCaller: { method: 'POST', path: '/agents/:slug/channel-bindings/:channel_account_id/callers/:caller_id/revoke', body: ['operation_id'], output: ['status', 'caller_id'] },
channelActivity: { method: 'GET', path: '/agents/:slug/channel-bindings/:channel_account_id/activity', query: { offset: 'offset', limit: 'limit' }, output: ['items', 'next_offset'], project: projectActivity },
channelConversations: { method: 'GET', path: '/channel-conversations', query: { slug: 'agent_slug', offset: 'offset' }, output: ['items', 'next_offset'], project: value => ({ items: list(object(value).items, projectSession), next_offset: object(value).next_offset ?? null }) },
channelConversation: { method: 'GET', path: '/channel-conversations/:session_id', query: { offset: 'offset' }, output: ['session_id', 'caller_id', 'agent_slug', 'messages', 'run', 'queued_requests', 'next_offset'], project: projectConversation },

View File

@@ -1,5 +1,12 @@
import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentConfiguration, type CloudAgentDraft, type CloudAgentPage, type CreateCloudAgent, type SaveCloudAgentDraft, type CloudUpload } from '../../shared/cloud-agents';
import { operationPlan } from './cloud-agent-operations';
const retiredChannelOperations = new Set([
'channelPolicy',
'createChannelPairing',
'channelCallers',
'revokeChannelCaller',
]);
import { CloudAgentJournal } from './cloud-agent-journal';
import type { CloudRecent } from '../../shared/cloud-agents';
import { WORKS_SQUARE_CONFIG } from '../api/works-config';
@@ -45,8 +52,6 @@ const MESSAGES: Record<string, string> = {
channel_session_control_failed: '微信会话操作失败,请刷新后重试',
channel_delivery_not_found: '渠道交付记录不存在',
channel_delivery_retry_failed: '文件补发失败,请刷新交付记录后重试',
self_pairing_removed: '本人无需配对,请直接使用绑定时扫码的微信发送消息',
pairing_expired: '邀请码已过期,请重新生成',
verification_required: '请先完成微信验证',
artifact_unavailable: '文件已不可用,请刷新文件列表',
operation_unavailable: '操作状态暂时无法确认,请稍后查询',
@@ -61,10 +66,6 @@ const MESSAGES: Record<string, string> = {
run_active: '当前会话仍在执行,请稍后重试',
run_not_active: '当前会话没有可操作的运行',
channel_session_unavailable: '新的微信会话暂时无法建立',
pairing_invalid: '配对请求无效,请刷新后重试',
invitation_invalid: '邀请码无效或已撤销',
invitation_consumed: '邀请码已经使用',
invites_disabled: '当前渠道仅允许本人使用',
agentbus_unavailable: '渠道服务暂时不可用,请稍后重试',
};
@@ -253,6 +254,10 @@ export class CloudAgentsModule {
await this.journal.remove(binding.accountKey, id, () => this.requireCurrent(binding));
return { discarded: true };
}
if (retiredChannelOperations.has(pending.operation)) {
await this.journal.remove(binding.accountKey, id, () => this.requireCurrent(binding));
return { operation: pending.operation, input: pending.input, discarded: true, retired: true };
}
if (pending.operation === 'wechatBindVerification' || pending.operation === 'channelAccountWechatBindVerification') {
// The verification code is intentionally never persisted. First recover the
// durable provider receipt: the code may already have been consumed even if
@@ -337,7 +342,7 @@ export class CloudAgentsModule {
'createChannelAccount', 'routeChannelAccount', 'enableChannelAccount', 'pauseChannelAccount', 'disconnectChannelAccount',
'channelAccountWechatBindStart', 'channelAccountWechatBindVerification', 'channelAccountWechatUnbind',
'createChannelBinding', 'switchChannelBindingAgent', 'enableChannelBinding', 'pauseChannelBinding', 'disconnectChannelBinding',
'channelPolicy', 'createChannelPairing', 'wechatBindStart', 'wechatBindVerification', 'wechatUnbind', 'revokeChannelCaller',
'wechatBindStart', 'wechatBindVerification', 'wechatUnbind',
'channelConversationControl', 'retryChannelDeliveryPart'];
let result: unknown;
if (recoverable.includes(plan.operation)) {