Skip to content

Latest commit

 

History

History
834 lines (698 loc) · 36.8 KB

File metadata and controls

834 lines (698 loc) · 36.8 KB

WeChat Remote Management: Product and Native Rust iLink Architecture

Status: Native production wiring implemented; activation is entitlement-gated Last updated: 2026-07-22 Decision owner: A3S Web Production release status: Code-complete for configured QR/read-only monitoring; public identity injection remains blocked on Tencent entitlement and a security review

Executive decision

A3S Web should provide built-in WeChat Remote Management backed by a native Rust WeixinModule inside A3S Boot. The runtime must use Tokio and reqwest directly. It must not launch Node.js, load OpenClaw, embed the OpenClaw plugin, or proxy control through a sidecar.

Product-facing English uses WeChat Remote Management. In the Chinese UI, the channel is labeled 微信 in the internal tab bar of 设置 → 渠道. The weixin spelling is retained only for stable internal Rust types, source paths, REST routes, compatibility hashes, and protocol evidence such as ilinkai.weixin.qq.com; those identifiers are not renamed for marketing terminology.

WeChat/Weixin iLink is a transport and a source of owner identity. It is not an A3S authorization system. A3S remains the sole owner of agent inventory, session state, permission policy, confirmations, execution, and audit records. Every inbound message is converted into a typed RemoteIntent; untrusted chat text never becomes a shell command, process signal, tool invocation, or control request directly.

The phrase “all agents on this machine” has three intentionally different truth levels:

Target level Evidence Remote capability
managed An A3S Web session owned by the current Code Web runtime Full supported session lifecycle, subject to local policy and confirmation
cooperative A fresh, exact A3S agent heartbeat with currently advertised actions Read status and invoke only the advertised, freshly revalidated action
observed Process discovery only Read-only, coarse process presence; no stdin injection, PID kill, or claimed task state

The first production release supports one bound owner, one bot account, direct text chats, read-only inventory by default, and locally enabled, step-up confirmed mutations. Group chats, arbitrary shell access, permanent remote deletion, and remote “approve always” are out of scope.

Product definition

User problem

An A3S user may leave a workstation while several sessions or child agents are working. Today the user must return to A3S Web or a terminal to answer a waiting agent, inspect progress, start a follow-up, or stop work. The WeChat connection should provide a narrow, trustworthy remote control surface without turning a chat message into general remote code execution.

Product goals

  • Bind WeChat by scanning a QR code from an A3S Web page.
  • Show a truthful inventory of managed, cooperative, and observed agents.
  • Answer conversational questions about task progress, execution state, attention requirements, and recent safe summaries.
  • Let the owner select a target and, when locally allowed, send a message, create a managed session, queue a turn, stop or cancel cooperative work, and archive a managed session.
  • Notify the owner about bounded events such as completion, failure, waiting for input, or waiting for approval.
  • Preserve the local A3S permission model and provide an audit trail for every remote decision.
  • Fail closed when identity, protocol state, target state, or action freshness is uncertain.

Non-goals

  • A generic WeChat bot framework or an OpenClaw compatibility layer.
  • Running an OpenClaw or Node.js process beside A3S.
  • Controlling an arbitrary process because its command line resembles an agent.
  • Sending signals to arbitrary PIDs or injecting bytes into a TTY/stdin.
  • Executing arbitrary shell, file, Git, MCP, or tool calls from chat text.
  • Treating a detected Codex, Claude, Gemini, Cursor, or similar process as a cooperatively controlled agent.
  • Group chats, multiple owners, shared family/team access, or multi-account routing in the first release.
  • Remote permanent purge, remote security-policy changes, or remote ApproveAlways.
  • File, image, video, or voice transfer in the first release.
  • Exposing complete transcripts, raw tool output, environment variables, command lines, absolute paths, secrets, or protocol tokens by default.

Primary persona

The initial persona is a single developer who owns both the workstation and the WeChat account used for QR binding. Team administration and delegated access require a separate multi-principal authorization design and are not an extension of this owner-only model.

Product surface

Placement

The feature is a trusted A3S Web product surface, not a sandboxed Web plugin. It needs privileged access to local policy and sanitized control-plane data; placing it in a plugin iframe would either make it ineffective or violate the plugin security boundary.

The shell exposes one Settings → Channels menu item. The Channels page owns an internal tab bar containing WeChat and Feishu; neither channel appears as a separate Settings navigation item. Feishu displays only “Coming soon” until its native adapter exists. The canonical WeChat route is #settings/channels/weixin; the legacy #settings/weixin and #weixin deep links open the same internal tab. The canonical Feishu route is #settings/channels/feishu. Channels are not top-level Activity Bar products and do not extend ProductId. The WeChat tab remains available in an “Unavailable” state when the binary lacks entitlement so that the reason is visible rather than silently hiding the feature.

Page layout

SettingsDialog
└── ChannelSettingsPage
    ├── ChannelTabList
    │   ├── WeChat
    │   └── Feishu
    ├── WeixinRemotePage (embedded tab panel)
    └── FeishuChannelPage (inactive tab panel)

WeixinRemotePage
    ├── ConnectionCard
    │   ├── entitlement and runtime health
    │   ├── bound owner/account summary
    │   ├── connect, pause/resume, and remove-from-this-machine actions
    │   └── last successful update and last error
    ├── QrLoginDialog
    │   ├── QR image, countdown, and scan state
    │   ├── optional pair-code challenge
    │   └── explicit owner/scope confirmation
    ├── RemoteAccessPolicyCard
    │   ├── locally editable scopes
    │   ├── confirmation rules
    │   ├── workspace allowlist aliases
    │   └── notification preferences
    ├── RemoteVisibleTargetsCard
    │   └── exact preview of what WeChat can currently see and do
    ├── DiagnosticsCard
    │   └── monitor, credential-store, cursor, and upstream status
    └── RemoteAuditList
        └── paged, sanitized decisions and outcomes

The page never renders or receives bot_token, context_token, QR polling identifiers, the effective authenticated base URL, control-grant tokens, or unmasked WeChat user identifiers. QR display content is the only ephemeral protocol material intentionally sent to the browser. A pair code remains only in component memory until submission and must be cleared immediately.

Connection states

UI state Meaning Available local action
unavailable A3S identity/configuration or secure runtime initialization is unavailable Read release-blocker guidance
unbound Entitled but no local credential exists Start QR binding
qr_pending QR is valid and awaiting scan Cancel or wait
scanned WeChat scanned the QR Wait for confirmation
verification_required Tencent requests a pair code Submit a bounded pair code
connecting Credential was received and is being stored/validated Wait; no duplicate bind
active Monitor is polling successfully Preview policy, pause, or disconnect
degraded Transient upstream, storage, or protocol problem Retry or inspect diagnostics
stale_credential iLink returned stale-token error -14 Rebind; monitor remains paused
paused The local owner paused remote access Resume locally or disconnect

“Remove from this machine” must be used unless Tencent exposes and authorizes a server-side revoke API. Deleting local credentials does not claim to invalidate a token at Tencent.

Chat experience

The owner can use concise commands or natural language. Deterministic commands are always available as a recovery path:

Example Typed result
帮助 Show supported commands and current permission scopes
智能体 List remote-visible targets with truth level and attention state
选择 2 Select a target in the owner’s remote conversation context
进度 Query selected target status and child-agent summary
会话 List managed sessions using safe titles/workspace aliases
最近回复 Return a bounded excerpt only when content-read scope is enabled
回复 请先运行测试 Draft a message/turn for the selected controllable target
新建 web 修复登录问题 Draft a managed session in the locally allowed web workspace alias
停止 Draft a supported managed/cooperative stop action
归档 Draft recoverable archive of the selected managed session
确认 7K4P Confirm one exact, unexpired mutation preview
取消确认 Cancel pending previews without executing them

Read-only queries return immediately. A mutating intent returns an exact preview containing the target, workspace alias, action, bounded text excerpt, risk explanation, expiry, and one-time confirmation code. The owner must send a second message to confirm. Confirmation never changes the requested action.

Ambiguous references produce a numbered disambiguation list. Unknown commands produce help or one clarifying question; they never fall through to a default agent or shell.

Core journeys

Bind an owner

  1. The local user opens WeChat Remote and reviews the warning that remote access can control this machine.
  2. A3S verifies entitlement, secure credential storage, loopback/admin protection, and the single-monitor lock.
  3. A3S creates one in-memory QR attempt and displays the returned QR content.
  4. The user scans with WeChat. If Tencent requests a pair code, the local page accepts a bounded code and sends it only to the Rust backend.
  5. On confirmation, A3S validates the returned hosts, stores the credential, binds the scanning ilink_user_id as the sole owner, and starts the monitor.
  6. Mutating scopes remain disabled until the user enables each one locally.

Check work away from the machine

  1. The owner sends “哪些智能体还在工作?”
  2. A3S authenticates the sender, deduplicates the update, and parses a ListTargets query.
  3. A3S obtains a fresh normalized snapshot and clearly labels exact versus inferred evidence.
  4. A bounded renderer replies with status, attention, safe workspace aliases, and freshness. It does not send raw process commands or absolute paths.

Reply to a waiting agent

  1. The owner selects an exact managed or cooperative target.
  2. “回复 使用方案 B” becomes a typed draft, not an immediate execution.
  3. Policy verifies the local scope and target capability, then returns a one-time preview.
  4. On confirmation, A3S fetches a fresh target snapshot, compares its stable action revision, obtains any current short-lived control grant internally, and executes exactly once or fails closed.
  5. A receipt states whether the message ran, queued, was rejected as stale, or has an unknown outcome after a crash.

Create and archive a session

Session creation accepts a local workspace alias, never an arbitrary path from WeChat. The initial prompt is a separately visible field in the confirmation preview. Archive is recoverable and does not call the existing permanent delete path. Permanent purge remains a local-only operation.

Protocol evidence and compatibility boundary

Evidence reviewed

The protocol observations in this document were derived from the published, MIT-licensed @tencent-weixin/openclaw-weixin package version 2.4.6, inspected on 2026-07-22. The inspected npm artifact has SHA-1 c7744c5b2d0232703c886b2f4e71681b0170695d and integrity sha512-qw9k3PLTiMWGNjjsknHgcTManH1w4j+Ji1ArWIaYLKCq3aFRsVwcqnPi127bvOoVMJGW4dbyJ8NECEMgoO+iRw==. That package is evidence of wire behavior, not proof that A3S is entitled to identify as OpenClaw or use the same production application ID.

Observed QR behavior:

  • fixed initial host https://ilinkai.weixin.qq.com;
  • POST ilink/bot/get_bot_qrcode?bot_type=3;
  • GET ilink/bot/get_qrcode_status?qrcode=...&verify_code=...;
  • QR creation uses the normal JSON POST headers, while QR polling uses only the application identity headers and does not send bearer authentication;
  • approximately five-minute local attempt lifetime and 35-second QR polling;
  • states wait, scaned, confirmed, expired, scaned_but_redirect, need_verifycode, verify_code_blocked, and binded_redirect (spelling preserved from the wire protocol);
  • confirmation fields including bot_token, ilink_bot_id, baseurl, and the scanning ilink_user_id;
  • an optional redirect_host while scanning; and
  • local_token_list in QR creation for already-bound detection.

Observed authenticated behavior:

  • AuthorizationType: ilink_bot_token;
  • bearer token authentication;
  • a random uint32 decimal string encoded as base64 in X-WECHAT-UIN;
  • iLink-App-Id and a packed iLink-App-ClientVersion header;
  • endpoints getupdates, sendmessage, getuploadurl, getconfig, sendtyping, msg/notifystart, and msg/notifystop under ilink/bot/;
  • sendmessage carries base_info beside the top-level msg object;
  • durable get_updates_buf cursor semantics;
  • server-provided longpolling_timeout_ms;
  • stale-token error -14; and
  • per-message context_token, which must be echoed in replies.

The message model exposes message_id, client_id, seq, from_user_id, to_user_id, group_id, message_type, item_list, context_token, and run_id. Text is item type 1; user and bot messages are separate message types. Media uses Tencent CDN metadata and AES-128-ECB in the observed package, which is one reason media is deferred.

Mandatory production gates

Before a production build enables real iLink traffic, Tencent must provide or confirm all of the following:

  1. An A3S-specific iLink-App-Id and the permitted bot_type. A3S must not copy iLink-App-Id: bot merely because the OpenClaw package uses it.
  2. The application/client version contract and an A3S-appropriate bot_agent identity. A3S must not claim to be OpenClaw.
  3. Service terms permitting an independent Rust implementation, endpoint use, owner binding, message retention, and machine-control use cases.
  4. Hostnames or a verifiable hostname policy for baseurl, redirect_host, CDN access if later enabled, and any regional routing.
  5. Rate limits, cursor retention behavior, message/client ID idempotency semantics, token lifecycle, revocation behavior, and a test/sandbox account.
  6. A supported response to error -14; the observed client pauses requests for one hour, but A3S should not infer that this refreshes or revokes a token.

The Rust production construction path is now present, but it activates only after an explicit A3S app ID and permitted bot type are supplied. Official release builds must not inject those values until these gates are satisfied. Local development and CI continue to use recorded, secret-free fixtures and a local mock server; automated tests never contact Tencent.

Activation configuration

Official builds can inject A3S_WEIXIN_ILINK_APP_ID and A3S_WEIXIN_ILINK_BOT_TYPE at compile time. A local operator with an approved A3S identity can provide the same process environment variables or reference them from config.acl:

channels {
  weixin {
    enabled = true
    app_id = env("A3S_WEIXIN_ILINK_APP_ID")
    bot_type = env("A3S_WEIXIN_ILINK_BOT_TYPE")
    allowed_hosts = ["ilinkai.weixin.qq.com"]
  }
}

client_version defaults to the A3S CLI version and bot_agent defaults to A3S/<version>; both can be supplied by an approved client contract. The QR origin remains compiled as https://ilinkai.weixin.qq.com/. ACL cannot replace that origin. allowed_hosts is an exact HTTPS hostname allowlist for validated redirect/account hosts, not an arbitrary browser-controlled endpoint list. OpenClaw's app_id = "bot" and an OpenClaw bot_agent are rejected.

Compatibility rules

  • The protocol is isolated behind IlinkTransport; no iLink DTO escapes into the A3S control-plane domain.
  • Unknown response states and enum values deserialize safely but fail closed at the application boundary.
  • Response bodies and arrays have explicit size/count limits.
  • Authenticated requests never follow redirects automatically.
  • Returned hosts are normalized and checked on every use: HTTPS only, no userinfo, no non-approved port, no IP literal, and an exact Tencent-approved hostname or approved subdomain suffix.
  • Test-only endpoint injection is compiled or constructed separately; a production user cannot configure an arbitrary base URL in ACL.
  • The initial QR request sends an empty local_token_list. A reconnect may send only the current A3S account token, loaded server-side, and only if Tencent confirms this is required. Tokens never pass through the browser.
  • Protocol request/response bodies and authentication headers are never logged.

Native Rust architecture

System context

flowchart LR
    WX[Owner in WeChat] <-->|iLink HTTPS| IL[IlinkTransport<br/>Rust reqwest]
    IL <--> WM[WeixinModule<br/>A3S Boot]
    WEB[A3S Web built-in page] <-->|local REST| WM
    WM --> RI[RemoteIntentRouter]
    RI --> PP[Policy + Confirmation]
    PP --> CP[RemoteAgentControlPlane]
    CP --> KS[Managed A3S Web sessions]
    CP --> CA[Cooperative A3S agents]
    CP --> OP[Observed process inventory]
    WM --> ST[Private state + credential store + audit]
Loading

There is no HTTP loopback from WeixinService to existing controllers. A3S Boot providers call narrow application ports directly so validation, policy, idempotency, and error types remain explicit.

Module placement

The backend belongs under src/api/code_web/weixin/ and is imported by CodeWebModule:

weixin/
├── mod.rs
├── module.rs
├── dto.rs
├── account_controller.rs
├── login_controller.rs
├── policy_controller.rs
├── audit_controller.rs
├── service.rs
├── login.rs
├── monitor.rs
├── credentials.rs
├── account_store.rs
├── runtime_store.rs
├── audit_store.rs
├── control_plane.rs
├── intent_router.rs
├── policy.rs
├── confirmation.rs
├── renderer.rs
├── domain/
│   ├── mod.rs
│   ├── account.rs
│   ├── target.rs
│   ├── intent.rs
│   ├── command.rs
│   └── audit.rs
└── ilink/
    ├── mod.rs
    ├── transport.rs
    ├── client.rs
    ├── auth.rs
    ├── types.rs
    ├── login.rs
    ├── updates.rs
    └── messages.rs

This is an implementation map, not permission to create empty abstractions. Files should be introduced with their phase and split by concern when they become real. Media handlers are deliberately absent until the media phase.

The frontend belongs under apps/web/src/features/weixin-remote/ with a thin page, controller hook, typed API adapter, and state-specific components. It is registered in the trusted application shell, never in plugin manifests.

A3S Boot provider graph

WeixinModule supplies or resolves these providers:

Provider Responsibility
IlinkEntitlement Approved app ID, bot type, client identity/version, and compiled host policy
IlinkTransport Typed protocol port used by application code and mock tests
TencentIlinkTransport reqwest implementation, JSON mapping, headers, timeouts, redaction, and URL enforcement
IlinkClient Account-scoped credential loading, endpoint calls, and protocol error mapping
WeixinLoginCoordinator One active QR attempt, polling, verification challenge, owner binding, and expiry
WeixinCredentialStore Secret envelope in OS keychain or explicit secure-file fallback
WeixinAccountStore Non-secret account state and local policy reference
WeixinRuntimeStore Cursor, inbox deduplication, outbox, selection, page-local opaque list context, confirmations, and command receipts
WeixinMonitorSupervisor Single-account long poll, cancellation, backoff, ingress/outbox workers, and health
RemoteAgentControlPlane Normalized target inventory and typed query/mutation port
RemoteIntentRouter Deterministic and constrained natural-language mapping to RemoteIntent
RemotePolicyService Owner, chat type, scope, target-level, state, and data-release decisions
RemoteConfirmationService One-time, owner-bound, expiring command previews
RemoteAuditStore Sanitized append-only security and action records
WeixinService Thin use-case facade for controllers and monitor

WeixinModule::on_application_bootstrap resolves the supervisor and starts it only when entitlement, binding, policy, secure storage, and the single-monitor lock are valid. on_application_shutdown cancels in-flight long polls, stops new intents, drains a bounded outbox window, persists the final cursor/runtime checkpoint, optionally sends notifystop, and joins every spawned task.

Tokio tasks use CancellationToken and a supervised JoinSet. There must be no detached forever-task and no blocking keychain, filesystem, or database call in an async executor thread. Any platform API that is inherently blocking runs in a bounded spawn_blocking call.

Existing A3S integration

The control plane must adapt existing sources rather than duplicate them:

  • KernelService remains the single source of truth for managed Web sessions, messages, turn queues, controls, goals, and child agents.
  • system_agents remains the source for exact cooperative presence and inferred process evidence.
  • the existing one-shot cooperative-control request protocol remains the only route for stop, cancel, deny, or reply actions advertised by a TUI.
  • process discovery remains read-only evidence and never becomes a process controller.

The Kernel and process modules should export narrow Rust ports through A3S Boot, for example ManagedSessionControl and SystemAgentControl, instead of making the WeChat adapter depend on controller JSON or the full CodeWebState. This keeps WeixinModule as an adapter and prevents two session implementations.

Managed remote archive requires a new recoverable archive operation. It must not call the current session DELETE endpoint if that endpoint permanently removes state. Managed message submission uses the existing turn queue: run when idle, queue when running, and return an authoritative receipt.

Protocol port

The exact Rust syntax may follow the crate's async-trait convention, but the port must be equivalent to:

pub trait IlinkTransport: Send + Sync {
    async fn create_qr(&self, request: CreateQrRequest) -> Result<CreateQrResponse>;
    async fn poll_qr(&self, request: PollQrRequest) -> Result<PollQrResponse>;
    async fn get_updates(&self, request: GetUpdatesRequest) -> Result<GetUpdatesResponse>;
    async fn send_message(&self, request: SendMessageRequest) -> Result<SendMessageResponse>;
    async fn get_config(&self, request: GetConfigRequest) -> Result<GetConfigResponse>;
    async fn send_typing(&self, request: SendTypingRequest) -> Result<SendTypingResponse>;
    async fn notify_start(&self, request: NotifyStartRequest) -> Result<()>;
    async fn notify_stop(&self, request: NotifyStopRequest) -> Result<()>;
}

get_upload_url is added only with the media phase. The production transport accepts validated endpoint classes, not arbitrary URL strings. The mock transport may use a local origin supplied by test construction.

reqwest::Client is built once with Rustls, automatic redirects disabled, a bounded connection pool, bounded response size, and proxy inheritance disabled by default for authenticated traffic. Normal calls have bounded timeouts. Long poll calls use the server-advised timeout within a Tencent-confirmed minimum and maximum plus a small client transport margin.

Control-plane contract

The protocol-independent application port should expose typed methods such as:

pub trait RemoteAgentControlPlane: Send + Sync {
    async fn snapshot(&self, scope: RemoteReadScope) -> Result<RemoteSnapshot>;
    async fn inspect(&self, target: RemoteTargetId) -> Result<RemoteTargetDetail>;
    async fn latest_reply(&self, target: RemoteTargetId) -> Result<SafeReplyExcerpt>;
    async fn prepare(&self, command: RemoteCommand) -> Result<PreparedRemoteCommand>;
    async fn execute(
        &self,
        prepared: PreparedRemoteCommand,
        idempotency_key: RemoteCommandId,
    ) -> Result<RemoteCommandReceipt>;
}

prepare resolves stable identities and returns a safe preview plus an opaque action_revision; it does not expose a one-shot agent-control token. execute retrieves live state again and compares the revision before acting.

The current cooperative grant TTL is approximately 10–12 seconds, shorter than a realistic WeChat confirmation flow. Therefore pending confirmations must never retain those tokens. On confirmation the adapter:

  1. resolves the same target and activity by stable logical IDs;
  2. verifies fresh exact heartbeat evidence;
  3. verifies that the action and sanitized context still match the stored action_revision;
  4. obtains the current unexpired grant token internally;
  5. writes one bounded one-shot request; and
  6. reports a stale-action error if any check races or fails.

An action_revision is an opaque digest/HMAC over semantic target state and available action context, excluding the short-lived token and expiry. The browser and WeChat see only the opaque revision or, preferably, no revision at all.

Domain model

Remote targets

pub enum RemoteTargetKind {
    ManagedSession,
    CooperativeAgent,
    ObservedProcess,
}

pub enum RemoteCapability {
    ReadStatus,
    ReadChildren,
    ReadSafeReply,
    SendMessage,
    CreateSession,
    ArchiveSession,
    Stop,
    Cancel,
    Reply,
    ApproveOnce,
    Deny,
}

Each normalized target contains an opaque RemoteTargetId, kind, display name, safe workspace alias, state, evidence confidence, freshness timestamp, parent relationship where exact, and current capabilities. It never uses a PID as a mutation identifier.

Observed processes always have only ReadStatus. Their status text must say that the process is detected and execution state is unknown. Command-line text, environment, raw cwd, and inferred task descriptions are not remotely exposed.

Remote intents

RemoteIntent is a closed enum divided into queries, drafts, confirmations, and conversation-context operations:

  • queries: help, list targets, inspect target, list sessions, get progress, and get latest safe reply;
  • context: select or clear a target and manage notification subscriptions;
  • command drafts: submit/queue a message, create a managed session, archive a session, stop, cancel, cooperative reply, approve once, or deny;
  • confirmation: confirm or cancel one pending command.

There is no Shell, KillPid, WriteFile, RunTool, ApproveAlways, or PurgeSession variant. Unsupported intent cannot be smuggled through an untyped map.

The parser follows this order:

  1. validate message envelope, owner, direct-chat shape, text type, and bounds;
  2. match deterministic commands;
  3. optionally use a locally configured LLM to produce a schema-constrained intent from a closed enum;
  4. validate every field and resolve user-facing references against current safe candidates; and
  5. return clarification when confidence or resolution is insufficient.

The LLM never receives protocol tokens, control grants, absolute paths, secrets, or raw process commands. An LLM-proposed mutation is still only a draft and always passes policy and confirmation.

Read-only inventories use deterministic 12-item pages. After rendering a page, the runtime journal records its kind, page number, timestamp, and only the opaque target IDs shown on that page. A later numeric selection resolves from that durable page-local context and then revalidates the ID against a fresh snapshot. It never maps the number onto a newly ordered global list. Missing or disappeared targets clear stale context, while duplicate safe titles produce a new bounded disambiguation context.

Local permission scopes

Scope Default after bind Confirmation Notes
agents.read On No Safe normalized inventory only
sessions.read On No Metadata, state, goal, queue, and safe summaries
sessions.content.read Off No Bounded latest reply; no raw tool output
sessions.message.write Off Always in v1 Submit or queue one user-reviewed message
sessions.create Off Always Workspace must be a local allowlist alias
sessions.archive Off Always Recoverable archive only
agents.control Off Always Only managed or freshly cooperative actions
approvals.decide_once Off Always Later phase; exact risk preview; no “always”
notifications.receive Off Locally configured Completion/failure/attention events only

Scopes and workspace aliases are editable only from the local Web page. No WeChat intent can grant, broaden, or persist its own permissions.

Confirmation record

A pending confirmation contains:

  • random confirmation ID and short display code;
  • owner and direct-chat binding;
  • exact typed command and sanitized preview;
  • resolved target ID and action_revision;
  • policy revision and required scope;
  • creation/expiry timestamps (two minutes by default);
  • inbound message idempotency key; and
  • state pending, executing, succeeded, failed, cancelled, expired, or outcome_unknown.

It is one-time, cannot be edited, and is invalidated by owner mismatch, target change, policy change, expiry, or successful consumption. At most three pending confirmations exist for the owner; older drafts expire first.

State machines and flows

QR login

stateDiagram-v2
    [*] --> Idle
    Idle --> Waiting: create QR
    Waiting --> Scanned: scaned
    Waiting --> Verify: need_verifycode
    Verify --> Waiting: valid pair code / scaned
    Verify --> Verify: verify_code_blocked with retries left
    Waiting --> Redirecting: scaned_but_redirect
    Redirecting --> Waiting: approved redirect host
    Waiting --> Confirming: confirmed
    Confirming --> Bound: credential and owner stored
    Waiting --> AlreadyBound: binded_redirect
    Waiting --> Expired: expired or local TTL
    Scanned --> Confirming: confirmed
    Scanned --> Expired: expired or local TTL
    Verify --> Expired: attempt/retry limit
    Redirecting --> Failed: invalid host
    Confirming --> Failed: invalid baseurl or secure-store failure
Loading

Only one QR attempt exists. Starting another attempt cancels the previous local state. qrcode, pending pair code, and polling host live in memory and are zeroized/dropped at terminal state. Unknown states fail the attempt visibly.

Inbound update pipeline

sequenceDiagram
    participant WX as WeChat iLink
    participant M as Monitor
    participant S as Runtime Store
    participant R as Intent Router
    participant P as Policy
    participant C as Control Plane
    participant O as Outbox

    M->>WX: getupdates(cursor)
    WX-->>M: messages + next cursor + timeout
    M->>M: validate response and owner envelope
    M->>S: durably stage message IDs and next cursor
    S-->>M: committed
    M->>R: bounded direct text message
    R-->>M: typed RemoteIntent
    M->>P: authorize intent against local policy
    alt read-only query
        P->>C: fresh query
        C-->>O: safe rendered response
    else mutation
        P->>S: persist confirmation preview
        S-->>O: confirmation message
    else rejected/ambiguous
        P-->>O: safe denial or clarification
    end
    O->>WX: sendmessage(stable client_id, context_token)
Loading

Cursor advancement and staged message identities are persisted before any side effect. A malformed message is quarantined with a reason and the batch may advance; one poison message cannot create an infinite poll loop.

An inbound mutation requires a stable protocol identity. Prefer message_id; otherwise use a documented client_id/seq composite. If Tencent cannot guarantee a stable identity, A3S may answer read-only queries but refuses the mutation.

Confirmed mutation

sequenceDiagram
    participant U as Owner
    participant W as WeixinService
    participant S as Runtime/Audit Store
    participant P as Policy
    participant C as Control Plane

    U->>W: 确认 7K4P
    W->>S: consume pending confirmation
    S-->>W: typed command + revisions
    W->>P: revalidate owner, scope, policy revision, expiry
    W->>C: prepare again from live target state
    C-->>W: live action revision and capability
    W->>W: constant-time revision comparison
    W->>S: reserve idempotency key and audit execution start
    W->>C: execute typed command once
    C-->>W: authoritative receipt
    W->>S: persist terminal receipt and audit outcome
    W-->>U: success, stale, rejected, or outcome unknown
Loading

If the process crashes after the durable executing reservation but before a terminal receipt, restart marks the command outcome_unknown and does not automatically replay it. The owner can inspect current state and create a new command. This prefers a possible missed action over duplicate message/session creation or repeated control.

Local REST API

Controllers are thin A3S Boot controllers and delegate to WeixinService. Suggested endpoints are:

Method Path Purpose
GET /api/v1/weixin/capability Entitlement, platform secure-store support, and release blockers
GET /api/v1/weixin/account Masked connection and monitor health
POST /api/v1/weixin/login-attempts Start the sole QR attempt
GET /api/v1/weixin/login-attempts/{attemptId} Read sanitized login state
POST /api/v1/weixin/login-attempts/{attemptId}/verification Submit pair code
DELETE /api/v1/weixin/login-attempts/{attemptId} Cancel attempt
POST /api/v1/weixin/account/pause Pause local monitor
POST /api/v1/weixin/account/resume Resume after local checks
DELETE /api/v1/weixin/account Stop and remove local binding/secret
GET /api/v1/weixin/policy Read effective remote scopes and aliases
PATCH /api/v1/weixin/policy Change local-only scopes/preferences
GET /api/v1/weixin/targets Preview exact remote-visible inventory
GET /api/v1/weixin/audit Cursor-paged sanitized audit records

All responses use the repository-wide wrapper. For example:

{
  "code": 200,
  "message": "Success",
  "data": {
    "state": "active",
    "ownerLabel": "WeChat owner ••••42",
    "mutationsEnabled": false,
    "lastUpdateAt": "2026-07-22T08:00:00.000Z"
  },
  "requestId": "uuid",
  "timestamp": "2026-07-22T08:00:01.000Z"
}

Business error codes include WEIXIN_NOT_ENTITLED, WEIXIN_NOT_BOUND, WEIXIN_LOGIN_EXPIRED, WEIXIN_VERIFICATION_REQUIRED, ILINK_STALE_CREDENTIAL, ILINK_PROTOCOL_CHANGED, ILINK_UPSTREAM_UNAVAILABLE, REMOTE_SCOPE_DISABLED, REMOTE_CONFIRMATION_EXPIRED, REMOTE_TARGET_CHANGED, REMOTE_ACTION_NOT_AVAILABLE, and REMOTE_ACTION_OUTCOME_UNKNOWN.

Login, credential removal, policy mutation, and audit endpoints require the same local administrator protection as other privileged A3S configuration. The feature refuses binding or mutation enablement when A3S Web is listening on a non-loopback interface without authenticated, origin-protected admin access. Default loopback binding is necessary but is not treated as a substitute for careful Origin/CSRF validation.

Security, operations, testing, and delivery

The detailed persistence design, threat model, failure recovery, privacy rules, service objectives, test matrix, phased rollout, and acceptance gates are maintained in the companion WeChat Remote Management Operations Plan. Both documents form one implementation decision and must be reviewed together.

Final architectural boundary

The implementation is not “WeChat can operate the machine.” It is “an authenticated owner can ask A3S to execute a small, typed, locally authorized set of control-plane operations through WeChat.” That distinction is the core security and product contract and must remain visible in code ownership, API types, UI copy, tests, and release review.