refactor(weixin-ilink): 措辞纠正 —「会话过期」→「会话被 iLink 拒绝」(零逻辑改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

事实(主人 2026-10-03 确认):iLink 的 bot token 是长期凭据(一个月前扫码的仍可用);errcode=-14 是 iLink 侧拒绝/作废该会话,不是时间性过期;且至今未观测到真实 -14(当日全部日志 0 次)。此前代码把这些写成「过期」,属沿用的旧命名,现纠正。
- 纯改名/文案:ErrSessionExpired→ErrSessionRejected、errSessionExpired→errcodeSessionRejected、expired/expiredToken→rejected/rejectedToken、markExpired/expiredLocked/expiredState→markRejected/rejectedLocked/rejectedState;409 错误码 session_expired→session_rejected
- 行为一字未改:显式标记 / Connect 不假 OK / 面板 needsRescan+lastError / 扫码或换新 token 即清除 全部保留
- 微信协议自身的「二维码过期」与 auth 的 JWT/refresh 过期一律未动(那些是真·时间性过期)
- Docs/api-client v0.39 补事实说明;顺手收尾 3 处残留注释(admin.go / channelbridge/inbound.go / readloop_test.go)
This commit is contained in:
AskaEth committed 2026-10-03 18:26:27 +08:00
1 parent cdc370dc82
commit a51aadeef4
14 files changed
+205 -192

No files matched your search

+24 -20
View File
@@ -63,8 +63,12 @@ const (
// ilinkVersion mirrors the upstream @tencent-weixin/openclaw-weixin version
// whose client version we advertise on the wire as iLink-App-ClientVersion.
ilinkVersion = "2.4.8"
// errSessionExpired is the iLink errcode for an expired bot token.
errSessionExpired = -14
// errcodeSessionRejected is the iLink errcode meaning iLink rejected or
// invalidated this session credential. It is NOT a time-based expiry: the bot
// token is a long-lived credential (a token obtained by scanning a month ago
// still works), so -14 means iLink no longer accepts this session — unbound,
// re-logged-in elsewhere, or refused at the protocol level.
errcodeSessionRejected = -14
)
// Sentinel errors returned by this adapter, usable with errors.Is.
@@ -73,8 +77,8 @@ var (
ErrInvalidConfig = errors.New("weixin_ilink: invalid config")
// ErrLoginTimeout is returned when the QR code is not confirmed in time.
ErrLoginTimeout = errors.New("weixin_ilink: qr login timed out")
// ErrSessionExpired is returned when iLink reports errcode=-14.
ErrSessionExpired = errors.New("weixin_ilink: session expired, re-scan required")
// ErrSessionRejected is returned when iLink reports errcode=-14.
ErrSessionRejected = errors.New("weixin_ilink: iLink rejected the session (errcode=-14); re-scan required")
// ErrNeedsLogin is returned by Connect when no bot_token is configured yet; the
// scan-code bind must be driven via the panel login-qr/login-status endpoints.
ErrNeedsLogin = errors.New("weixin_ilink: bot not logged in yet, scan the QR")
@@ -159,15 +163,15 @@ type Adapter struct {
mu sync.RWMutex
connected bool
// expired marks a session iLink rejected with errcode=-14: the bot token is
// dead and only a fresh scan-code login re-binds it (session_state.go).
// expiredToken records WHICH token was rejected, so a subsequently configured
// rejected marks a session iLink rejected with errcode=-14: the credential is
// refused until a fresh scan-code login re-binds it (session_state.go).
// rejectedToken records WHICH token was rejected, so a subsequently configured
// token clears the state; rescanDetail is the panel-visible reason. All three
// are guarded by mu.
expired bool
expiredToken string
rescanDetail string
qrcode string
rejected bool
rejectedToken string
rescanDetail string
qrcode string
// pollBaseURL optionally overrides cfg.BaseURL for get_qrcode_status polling.
// It is set when the server returns scaned_but_redirect with a redirect_host,
// so subsequent status polls follow the IDC host. Guarded by mu.
@@ -242,14 +246,14 @@ func (a *Adapter) Config() map[string]any {
// runs the scan-code login flow (see login.go).
//
// A token iLink already rejected with errcode=-14 is NOT usable: Connect refuses
// it with ErrSessionExpired (re-scan required) instead of reporting a session
// it with ErrSessionRejected (re-scan required) instead of reporting a session
// that the very next poll would tear down, and the scan-code login flow is the
// way back (see login_admin.go StartLogin).
func (a *Adapter) Connect(ctx context.Context) error {
if expired, reason := a.expiredState(); expired {
a.log.Error("ilink connect refused: session expired, 需要重新扫码登录",
if rejected, reason := a.rejectedState(); rejected {
a.log.Error("ilink connect refused: session rejected, 需要重新扫码绑定(请用「扫码登录」)",
"bot_id", a.botID(), "reason", reason)
return fmt.Errorf("%w: %s", ErrSessionExpired, reason)
return fmt.Errorf("%w: %s", ErrSessionRejected, reason)
}
a.mu.RLock()
connected := a.connected
@@ -300,14 +304,14 @@ func (a *Adapter) IsConnected() bool {
}
// HealthCheck verifies the session is usable: it must be connected and carry a
// bot_token. An expired session (iLink rejected the token with errcode=-14) is
// reported as ErrSessionExpired — "re-scan required" — so a caller can tell a
// dead credential apart from a plain disconnected adapter. It does not probe the
// bot_token. A session iLink rejected (errcode=-14) is reported as
// ErrSessionRejected — "re-scan required" — so a caller can tell a refused
// credential apart from a plain disconnected adapter. It does not probe the
// network so callers can use it cheaply after a reconnect without incurring a
// request.
func (a *Adapter) HealthCheck(ctx context.Context) error {
if expired, reason := a.expiredState(); expired {
return fmt.Errorf("%w: %s", ErrSessionExpired, reason)
if rejected, reason := a.rejectedState(); rejected {
return fmt.Errorf("%w: %s", ErrSessionRejected, reason)
}
a.mu.RLock()
defer a.mu.RUnlock()