@harrylabsj/kiwi 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/a2a/node.d.ts +3 -1
  2. package/dist/a2a/node.js +1 -0
  3. package/dist/a2a/node.js.map +1 -1
  4. package/dist/agent/buyer/types.d.ts +0 -25
  5. package/dist/agent/buyer/types.js.map +1 -1
  6. package/dist/agent/kernel-builder.d.ts +27 -0
  7. package/dist/agent/kernel-builder.js +180 -0
  8. package/dist/agent/kernel-builder.js.map +1 -0
  9. package/dist/cli.js +98 -154
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commerce/data-source.d.ts +4 -3
  12. package/dist/commerce/data-source.js.map +1 -1
  13. package/dist/commerce/shopping-cli-source.js +3 -2
  14. package/dist/commerce/shopping-cli-source.js.map +1 -1
  15. package/dist/config/profile.d.ts +7 -0
  16. package/dist/config/profile.js +23 -0
  17. package/dist/config/profile.js.map +1 -1
  18. package/dist/discovery/catalog-source/kiwi-record.d.ts +3 -0
  19. package/dist/discovery/catalog-source/kiwi-record.js.map +1 -1
  20. package/dist/discovery/catalog-source/kiwi-source.js +7 -1
  21. package/dist/discovery/catalog-source/kiwi-source.js.map +1 -1
  22. package/dist/discovery/catalog-source/register.d.ts +6 -0
  23. package/dist/discovery/catalog-source/register.js +15 -7
  24. package/dist/discovery/catalog-source/register.js.map +1 -1
  25. package/dist/handoff/errors.d.ts +1 -1
  26. package/dist/handoff/errors.js +1 -0
  27. package/dist/handoff/errors.js.map +1 -1
  28. package/dist/handoff/idempotency.d.ts +10 -1
  29. package/dist/handoff/idempotency.js +13 -3
  30. package/dist/handoff/idempotency.js.map +1 -1
  31. package/dist/handoff/transaction.js +13 -6
  32. package/dist/handoff/transaction.js.map +1 -1
  33. package/dist/negotiation/state/phase.js +12 -4
  34. package/dist/negotiation/state/phase.js.map +1 -1
  35. package/dist/product-catalog.d.ts +43 -0
  36. package/dist/product-catalog.js +60 -0
  37. package/dist/product-catalog.js.map +1 -0
  38. package/dist/product-cli.d.ts +1 -1
  39. package/dist/product-cli.js +1 -1
  40. package/dist/product-publish.d.ts +10 -17
  41. package/dist/product-publish.js +177 -41
  42. package/dist/product-publish.js.map +1 -1
  43. package/dist/weixin/channel.d.ts +87 -0
  44. package/dist/weixin/channel.js +356 -0
  45. package/dist/weixin/channel.js.map +1 -0
  46. package/dist/weixin/cli-weixin.d.ts +29 -0
  47. package/dist/weixin/cli-weixin.js +128 -0
  48. package/dist/weixin/cli-weixin.js.map +1 -0
  49. package/dist/weixin/credentials.d.ts +33 -0
  50. package/dist/weixin/credentials.js +107 -0
  51. package/dist/weixin/credentials.js.map +1 -0
  52. package/dist/weixin/ilink-client.d.ts +44 -0
  53. package/dist/weixin/ilink-client.js +262 -0
  54. package/dist/weixin/ilink-client.js.map +1 -0
  55. package/dist/weixin/login.d.ts +34 -0
  56. package/dist/weixin/login.js +93 -0
  57. package/dist/weixin/login.js.map +1 -0
  58. package/dist/weixin/qr.d.ts +26 -0
  59. package/dist/weixin/qr.js +525 -0
  60. package/dist/weixin/qr.js.map +1 -0
  61. package/dist/weixin/types.d.ts +114 -0
  62. package/dist/weixin/types.js +48 -0
  63. package/dist/weixin/types.js.map +1 -0
  64. package/package.json +4 -1
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Copyright 2026 harrylabsj
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * 微信通道凭证与同步游标持久化——0600、原子写、损坏 fail-closed。
18
+ *
19
+ * - `weixin-credentials.json`:bot 凭证(token/base_url/user_id/saved_at);
20
+ * - `weixin-sync-buf.json`:getupdates 游标 + 去重指纹(重启零丢失)。
21
+ * 写文件用 tmp + renameSync 原子替换(防崩溃半写);损坏/缺字段一律
22
+ * WeixinError(fail-closed,拒绝静默重扫)。
23
+ */
24
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
25
+ import path from "node:path";
26
+ import { WeixinError } from "./types.js";
27
+ /** 保存凭证(0600 + 原子写)。 */
28
+ export function saveCredentials(pathName, creds) {
29
+ const payload = {
30
+ ilink_bot_id: creds.ilink_bot_id,
31
+ bot_token: creds.bot_token,
32
+ base_url: creds.base_url,
33
+ ilink_user_id: creds.ilink_user_id,
34
+ saved_at: creds.saved_at,
35
+ };
36
+ atomicWrite(pathName, JSON.stringify(payload));
37
+ }
38
+ /** 加载凭证;缺失 → not_configured;损坏/缺字段 → validation(fail-closed)。 */
39
+ export function loadCredentials(pathName) {
40
+ if (!existsSync(pathName)) {
41
+ throw new WeixinError("not_configured", `微信凭证不存在:${pathName}(请先 kiwi weixin 扫码登录)`);
42
+ }
43
+ let raw;
44
+ try {
45
+ raw = JSON.parse(readFileSync(pathName, "utf-8"));
46
+ }
47
+ catch {
48
+ throw new WeixinError("validation", `微信凭证文件损坏:${pathName}`);
49
+ }
50
+ if (!isRecord(raw))
51
+ throw new WeixinError("validation", `微信凭证文件格式错误:${pathName}`);
52
+ for (const key of ["ilink_bot_id", "bot_token", "base_url", "ilink_user_id", "saved_at"]) {
53
+ if (typeof raw[key] !== "string") {
54
+ throw new WeixinError("validation", `微信凭证缺少字段 ${key}:${pathName}`);
55
+ }
56
+ }
57
+ return {
58
+ ilink_bot_id: raw.ilink_bot_id,
59
+ bot_token: raw.bot_token,
60
+ base_url: raw.base_url,
61
+ ilink_user_id: raw.ilink_user_id,
62
+ saved_at: raw.saved_at,
63
+ };
64
+ }
65
+ /** 保存同步游标 + 去重指纹(0600 + 原子写)。 */
66
+ export function saveSyncState(pathName, state) {
67
+ atomicWrite(pathName, JSON.stringify(state));
68
+ }
69
+ /** 加载同步游标;缺失 → 默认(空游标);损坏 → validation(fail-closed)。 */
70
+ export function loadSyncState(pathName) {
71
+ if (!existsSync(pathName)) {
72
+ return { get_updates_buf: "", seen: [] };
73
+ }
74
+ let raw;
75
+ try {
76
+ raw = JSON.parse(readFileSync(pathName, "utf-8"));
77
+ }
78
+ catch {
79
+ throw new WeixinError("validation", `微信同步状态文件损坏:${pathName}`);
80
+ }
81
+ if (!isRecord(raw))
82
+ throw new WeixinError("validation", `微信同步状态文件格式错误:${pathName}`);
83
+ const buf = raw.get_updates_buf;
84
+ const seen = raw.seen;
85
+ if (typeof buf !== "string" || !Array.isArray(seen) || !seen.every((s) => typeof s === "string")) {
86
+ throw new WeixinError("validation", `微信同步状态字段错误:${pathName}`);
87
+ }
88
+ return { get_updates_buf: buf, seen: seen };
89
+ }
90
+ // ── 内部:原子写(tmp + renameSync,0600)─────────────────────────────
91
+ function atomicWrite(pathName, content) {
92
+ const tmp = `${pathName}.tmp`;
93
+ writeFileSync(tmp, content, { mode: 0o600 });
94
+ renameSync(tmp, pathName);
95
+ }
96
+ function isRecord(v) {
97
+ return typeof v === "object" && v !== null && !Array.isArray(v);
98
+ }
99
+ /** 便捷:凭证文件路径(dataDir 下)。 */
100
+ export function credentialsPathFor(dataDir) {
101
+ return path.join(dataDir, "weixin-credentials.json");
102
+ }
103
+ /** 便捷:同步状态文件路径(dataDir 下)。 */
104
+ export function syncStatePathFor(dataDir) {
105
+ return path.join(dataDir, "weixin-sync-buf.json");
106
+ }
107
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/weixin/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Copyright 2026 harrylabsj
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import type { BotCredentials, BotQrcode, GetUpdatesResult, QrcodeStatus, SendMessageInput } from "./types.js";
17
+ export interface IlinkClientOptions {
18
+ /** iLink base URL(confirmed 的 baseurl 或默认)。 */
19
+ baseUrl?: string;
20
+ log?: (line: string) => void;
21
+ }
22
+ /**
23
+ * iLink 客户端:登录三端点 + 消息收发。所有请求带 iLink 头,响应严格
24
+ * 解析(未知字段忽略,必需字段缺失 fail-closed),错误类型化分类。
25
+ */
26
+ export declare class IlinkClient {
27
+ private baseUrl;
28
+ private readonly log?;
29
+ /** sendmessage 的 client_id(每进程一个,幂等标识)。 */
30
+ private readonly clientId;
31
+ constructor(options?: IlinkClientOptions);
32
+ /** scaned_but_redirect 时切换 base URL(进程内生效;持久化由调用方负责)。 */
33
+ setBaseUrl(url: string): void;
34
+ get base(): string;
35
+ /** GET ilink/bot/get_bot_qrcode?bot_type=3 → 二维码(hex token + 完整可扫 URL)。 */
36
+ getBotQrcode(): Promise<BotQrcode>;
37
+ /** GET ilink/bot/get_qrcode_status?qrcode=<hex> → 扫码状态(判别联合)。 */
38
+ getQrcodeStatus(qrcode: string): Promise<QrcodeStatus>;
39
+ /** POST ilink/bot/getupdates(长轮询)→ 消息 + 新游标。 */
40
+ getUpdates(syncBuf: string, credentials: BotCredentials): Promise<GetUpdatesResult>;
41
+ /** POST ilink/bot/sendmessage → 回显 context_token(协议要求)。 */
42
+ sendMessage(input: SendMessageInput, credentials: BotCredentials): Promise<void>;
43
+ private request;
44
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Copyright 2026 harrylabsj
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * iLink Bot API 客户端(微信远程控制通道)——腾讯 iLink 协议实现。
18
+ *
19
+ * 协议学习自 Hermes Agent weixin adapter(gateway/platforms/weixin.py):
20
+ * - 登录:get_bot_qrcode → 轮询 get_qrcode_status → confirmed 得凭证;
21
+ * - 消息:getupdates 长轮询(get_updates_buf 游标);sendmessage 回显 context_token;
22
+ * - 头:iLink-App-Id: "bot"、iLink-App-ClientVersion: "131584"(=(2<<16)|(2<<8)|0);
23
+ * - 错误:ret/errcode 非 0 → protocol;-14 或 (-2+"unknown error") → session_stale。
24
+ * 出站纪律全走 safe-http(manual redirect / 超时覆盖 body / 1 MiB 上限)。
25
+ */
26
+ import { isRedirectResponse, readJsonBody, SafeHttpError } from "../net/safe-http.js";
27
+ import { ILINK_DEFAULT_BASE_URL, WeixinError } from "./types.js";
28
+ /** iLink 端点(相对 base URL)。 */
29
+ const EP_GET_UPDATES = "ilink/bot/getupdates";
30
+ const EP_SEND_MESSAGE = "ilink/bot/sendmessage";
31
+ const EP_GET_BOT_QR = "ilink/bot/get_bot_qrcode";
32
+ const EP_GET_QR_STATUS = "ilink/bot/get_qrcode_status";
33
+ /** 协议常量(Hermes weixin.py 对齐)。 */
34
+ const ILINK_APP_ID = "bot";
35
+ const ILINK_APP_CLIENT_VERSION = String((2 << 16) | (2 << 8) | 0); // "131584"
36
+ const MSG_TYPE_BOT = 2;
37
+ const MSG_STATE_FINISH = 2;
38
+ const ITEM_TEXT = 1;
39
+ const LONG_POLL_DEFAULT_MS = 35_000;
40
+ const API_TIMEOUT_MS = 15_000;
41
+ const QR_TIMEOUT_MS = 35_000;
42
+ const MAX_RESPONSE_BYTES = 1024 * 1024; // 1 MiB(iLink 响应远小于默认 8 MiB)
43
+ const SESSION_EXPIRED_ERRCODE = -14;
44
+ const RATE_LIMIT_ERRCODE = -2;
45
+ /**
46
+ * iLink 客户端:登录三端点 + 消息收发。所有请求带 iLink 头,响应严格
47
+ * 解析(未知字段忽略,必需字段缺失 fail-closed),错误类型化分类。
48
+ */
49
+ export class IlinkClient {
50
+ baseUrl;
51
+ log;
52
+ /** sendmessage 的 client_id(每进程一个,幂等标识)。 */
53
+ clientId;
54
+ constructor(options = {}) {
55
+ this.baseUrl = (options.baseUrl ?? ILINK_DEFAULT_BASE_URL).replace(/\/+$/, "");
56
+ this.log = options.log;
57
+ this.clientId = crypto.randomUUID();
58
+ }
59
+ /** scaned_but_redirect 时切换 base URL(进程内生效;持久化由调用方负责)。 */
60
+ setBaseUrl(url) {
61
+ this.baseUrl = url.replace(/\/+$/, "");
62
+ }
63
+ get base() {
64
+ return this.baseUrl;
65
+ }
66
+ /** GET ilink/bot/get_bot_qrcode?bot_type=3 → 二维码(hex token + 完整可扫 URL)。 */
67
+ async getBotQrcode() {
68
+ const body = (await this.request("GET", `${EP_GET_BOT_QR}?bot_type=3`, {
69
+ timeoutMs: QR_TIMEOUT_MS,
70
+ }));
71
+ const qrcode = body.qrcode;
72
+ const qrcodeImgContent = body.qrcode_img_content;
73
+ if (typeof qrcode !== "string" || qrcode === "" || typeof qrcodeImgContent !== "string") {
74
+ throw new WeixinError("validation", "get_bot_qrcode 响应缺少 qrcode/qrcode_img_content");
75
+ }
76
+ return { qrcode, qrcode_img_content: qrcodeImgContent };
77
+ }
78
+ /** GET ilink/bot/get_qrcode_status?qrcode=<hex> → 扫码状态(判别联合)。 */
79
+ async getQrcodeStatus(qrcode) {
80
+ const body = (await this.request("GET", `${EP_GET_QR_STATUS}?qrcode=${encodeURIComponent(qrcode)}`, {
81
+ timeoutMs: QR_TIMEOUT_MS,
82
+ }));
83
+ const status = String(body.status ?? "");
84
+ if (status === "confirmed") {
85
+ const accountId = body.ilink_bot_id;
86
+ const token = body.bot_token;
87
+ const baseUrl = body.baseurl;
88
+ const userId = body.ilink_user_id;
89
+ if (typeof accountId !== "string" || accountId === "" || typeof token !== "string" || token === "") {
90
+ throw new WeixinError("validation", "get_qrcode_status confirmed 缺少凭证字段");
91
+ }
92
+ const credentials = {
93
+ ilink_bot_id: accountId,
94
+ bot_token: token,
95
+ base_url: typeof baseUrl === "string" && baseUrl !== "" ? baseUrl : ILINK_DEFAULT_BASE_URL,
96
+ ilink_user_id: typeof userId === "string" ? userId : "",
97
+ saved_at: "",
98
+ };
99
+ return { state: "confirmed", credentials };
100
+ }
101
+ if (status === "scaned_but_redirect") {
102
+ const redirectHost = String(body.redirect_host ?? "");
103
+ if (redirectHost === "") {
104
+ throw new WeixinError("validation", "scaned_but_redirect 缺少 redirect_host");
105
+ }
106
+ return { state: "scaned_but_redirect", baseUrl: `https://${redirectHost}` };
107
+ }
108
+ if (status === "wait" || status === "scaned" || status === "expired") {
109
+ return { state: status };
110
+ }
111
+ throw new WeixinError("validation", `get_qrcode_status 未知状态 "${status}"`);
112
+ }
113
+ /** POST ilink/bot/getupdates(长轮询)→ 消息 + 新游标。 */
114
+ async getUpdates(syncBuf, credentials) {
115
+ const body = (await this.request("POST", EP_GET_UPDATES, {
116
+ timeoutMs: LONG_POLL_DEFAULT_MS + 20_000, // 长轮询需覆盖服务端 longpoll
117
+ payload: {
118
+ get_updates_buf: syncBuf,
119
+ base_info: baseInfoOf(credentials),
120
+ },
121
+ }));
122
+ assertOk(body, "getupdates");
123
+ const messages = [];
124
+ for (const raw of Array.isArray(body.msgs) ? body.msgs : []) {
125
+ if (!isRecord(raw))
126
+ continue;
127
+ const text = extractText(raw);
128
+ const message = {
129
+ account: String(raw.from_user_id ?? ""),
130
+ from_user_id: String(raw.from_user_id ?? ""),
131
+ message_id: String(raw.message_id ?? ""),
132
+ text,
133
+ context_token: String(raw.context_token ?? ""),
134
+ };
135
+ messages.push(message);
136
+ }
137
+ const nextSyncBuf = String(body.get_updates_buf ?? syncBuf);
138
+ const longpoll = Number(body.longpolling_timeout_ms ?? LONG_POLL_DEFAULT_MS);
139
+ return {
140
+ messages,
141
+ next_sync_buf: nextSyncBuf,
142
+ longpoll_timeout_ms: Number.isFinite(longpoll) && longpoll > 0 ? longpoll : LONG_POLL_DEFAULT_MS,
143
+ };
144
+ }
145
+ /** POST ilink/bot/sendmessage → 回显 context_token(协议要求)。 */
146
+ async sendMessage(input, credentials) {
147
+ const message = {
148
+ from_user_id: "",
149
+ to_user_id: input.to_user_id,
150
+ client_id: this.clientId,
151
+ message_type: MSG_TYPE_BOT,
152
+ message_state: MSG_STATE_FINISH,
153
+ item_list: [{ type: ITEM_TEXT, text_item: { text: input.text } }],
154
+ };
155
+ if (input.context_token !== undefined && input.context_token !== "") {
156
+ message.context_token = input.context_token;
157
+ }
158
+ const body = (await this.request("POST", EP_SEND_MESSAGE, {
159
+ timeoutMs: API_TIMEOUT_MS,
160
+ payload: { msg: message, base_info: baseInfoOf(credentials) },
161
+ }));
162
+ assertOk(body, "sendmessage");
163
+ }
164
+ // ── 内部:统一请求(safe-http 纪律)──────────────────────────────────
165
+ async request(method, path, options) {
166
+ const controller = new AbortController();
167
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs);
168
+ try {
169
+ const headers = {
170
+ "iLink-App-Id": ILINK_APP_ID,
171
+ "iLink-App-ClientVersion": ILINK_APP_CLIENT_VERSION,
172
+ };
173
+ const init = {
174
+ method,
175
+ headers,
176
+ signal: controller.signal,
177
+ redirect: "manual",
178
+ };
179
+ if (options.payload !== undefined) {
180
+ headers["content-type"] = "application/json";
181
+ init.body = JSON.stringify(options.payload);
182
+ }
183
+ const response = await fetch(`${this.baseUrl}/${path}`, init);
184
+ if (isRedirectResponse(response)) {
185
+ throw new WeixinError("redirect", `iLink ${path} 重定向被拒绝 (${response.status})`);
186
+ }
187
+ if (!response.ok) {
188
+ throw new WeixinError("http", `iLink ${path} HTTP ${response.status}`);
189
+ }
190
+ try {
191
+ return await readJsonBody(response, { maxBytes: MAX_RESPONSE_BYTES, signal: controller.signal });
192
+ }
193
+ catch (err) {
194
+ if (err instanceof SafeHttpError) {
195
+ if (err.code === "response_too_large")
196
+ throw new WeixinError("response_too_large", err.message);
197
+ if (err.code === "invalid_json")
198
+ throw new WeixinError("invalid_json", err.message);
199
+ }
200
+ throw err;
201
+ }
202
+ }
203
+ catch (err) {
204
+ if (err instanceof WeixinError)
205
+ throw err;
206
+ if (err instanceof Error && err.name === "AbortError") {
207
+ throw new WeixinError("timeout", `iLink ${path} 超时 (${options.timeoutMs}ms)`);
208
+ }
209
+ throw new WeixinError("network", `iLink ${path} 网络错误: ${err instanceof Error ? err.message : String(err)}`);
210
+ }
211
+ finally {
212
+ clearTimeout(timer);
213
+ }
214
+ }
215
+ }
216
+ /** base_info 身份三件套(getupdates/sendmessage 通用)。 */
217
+ function baseInfoOf(credentials) {
218
+ return {
219
+ ilink_bot_id: credentials.ilink_bot_id,
220
+ bot_token: credentials.bot_token,
221
+ ilink_user_id: credentials.ilink_user_id,
222
+ };
223
+ }
224
+ /** 解析 ret/errcode(-14 / -2+"unknown error" → session_stale;其余 → protocol)。 */
225
+ function assertOk(body, endpoint) {
226
+ const ret = body.ret;
227
+ const errcode = body.errcode;
228
+ if (ret === undefined && errcode === undefined)
229
+ return;
230
+ const retNum = Number(ret ?? 0);
231
+ const errNum = Number(errcode ?? 0);
232
+ if (retNum === 0 && errNum === 0)
233
+ return;
234
+ if (retNum === SESSION_EXPIRED_ERRCODE || errNum === SESSION_EXPIRED_ERRCODE) {
235
+ throw new WeixinError("session_stale", `${endpoint}: 会话过期 (ret=${ret}, errcode=${errcode})`);
236
+ }
237
+ if ((retNum === RATE_LIMIT_ERRCODE || errNum === RATE_LIMIT_ERRCODE) &&
238
+ String(body.errmsg ?? "").toLowerCase() === "unknown error") {
239
+ throw new WeixinError("session_stale", `${endpoint}: 会话过期 (ret=-2 unknown error)`);
240
+ }
241
+ throw new WeixinError("protocol", `${endpoint}: ret=${ret} errcode=${errcode} errmsg=${String(body.errmsg ?? "")}`);
242
+ }
243
+ /** 从 item_list 提取文本(type:1 的 text_item.text;非文本消息返回空串)。 */
244
+ function extractText(raw) {
245
+ const items = raw.item_list;
246
+ if (!Array.isArray(items))
247
+ return "";
248
+ for (const item of items) {
249
+ if (!isRecord(item))
250
+ continue;
251
+ if (item.type === ITEM_TEXT) {
252
+ const textItem = item.text_item;
253
+ if (isRecord(textItem) && typeof textItem.text === "string")
254
+ return textItem.text;
255
+ }
256
+ }
257
+ return "";
258
+ }
259
+ function isRecord(v) {
260
+ return typeof v === "object" && v !== null && !Array.isArray(v);
261
+ }
262
+ //# sourceMappingURL=ilink-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ilink-client.js","sourceRoot":"","sources":["../../src/weixin/ilink-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Copyright 2026 harrylabsj
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import type { BotCredentials } from "./types.js";
17
+ import type { IlinkClient } from "./ilink-client.js";
18
+ export interface LoginOptions {
19
+ /** 二维码渲染输出(缺省打印到 stdout)。 */
20
+ render?: (qr: string[]) => void;
21
+ /** 提示行输出(缺省 stderr)。 */
22
+ notice?: (line: string) => void;
23
+ /** 扫码轮询间隔(毫秒;缺省 DEFAULT_WEIXIN_TIMINGS.qrPollMs)。 */
24
+ qrPollMs?: number;
25
+ /** 过期刷新上限(缺省 DEFAULT_WEIXIN_TIMINGS.qrRefreshCap)。 */
26
+ qrRefreshCap?: number;
27
+ /** 时间源(测试注入)。 */
28
+ now?: () => string;
29
+ }
30
+ /**
31
+ * 执行扫码登录并返回凭证(调用方负责持久化)。
32
+ * 失败:网络/协议错抛 WeixinError;多次过期抛 qr_expired。
33
+ */
34
+ export declare function loginWithQrcode(client: IlinkClient, options?: LoginOptions): Promise<BotCredentials>;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Copyright 2026 harrylabsj
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * 微信扫码登录流程(iLink Bot API)——取码 → 渲染 QR → 轮询状态 →
18
+ * confirmed 落盘凭证。expired 自动刷新(≤3 次);scaned_but_redirect
19
+ * 切换 base URL。登录成功后向配对用户发一条"通道就绪"提示(协议上
20
+ * 首次主动消息可能不可达——用户先发一句任意消息即"唤醒")。
21
+ */
22
+ import { renderQr } from "./qr.js";
23
+ import { DEFAULT_WEIXIN_TIMINGS, WeixinError } from "./types.js";
24
+ /**
25
+ * 执行扫码登录并返回凭证(调用方负责持久化)。
26
+ * 失败:网络/协议错抛 WeixinError;多次过期抛 qr_expired。
27
+ */
28
+ export async function loginWithQrcode(client, options = {}) {
29
+ const render = options.render ?? ((qr) => {
30
+ for (const line of qr)
31
+ process.stdout.write(`${line}\n`);
32
+ });
33
+ const notice = options.notice ?? ((line) => process.stderr.write(`${line}\n`));
34
+ const pollMs = options.qrPollMs ?? DEFAULT_WEIXIN_TIMINGS.qrPollMs;
35
+ const refreshCap = options.qrRefreshCap ?? DEFAULT_WEIXIN_TIMINGS.qrRefreshCap;
36
+ const now = options.now ?? (() => new Date().toISOString());
37
+ let refreshCount = 0;
38
+ for (;;) {
39
+ const qrcode = await client.getBotQrcode();
40
+ render(renderQr(qrcode.qrcode_img_content));
41
+ notice("请使用微信扫一扫登录 kiwi 微信通道(8 分钟内有效,过期自动刷新)");
42
+ // 轮询状态直到 confirmed / expired(自动刷新)
43
+ const credentials = await pollUntilConfirmed(client, qrcode.qrcode, {
44
+ pollMs,
45
+ notice,
46
+ onExpired: async () => {
47
+ refreshCount += 1;
48
+ if (refreshCount > refreshCap) {
49
+ throw new WeixinError("qr_expired", `二维码多次过期(>${refreshCap} 次),请重新执行 kiwi weixin`);
50
+ }
51
+ notice(`二维码已过期,正在刷新(${refreshCount}/${refreshCap})…`);
52
+ return true; // 外层 for 循环重新取码
53
+ },
54
+ });
55
+ if (credentials !== null) {
56
+ credentials.saved_at = now();
57
+ notice("微信连接成功,等待消息…(首次请先发一句任意消息唤醒通道)");
58
+ return credentials;
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * 轮询扫码状态直到 confirmed;expired 时回调 onExpired(返回 true 重新取码)。
64
+ * 返回 null 表示 expired 且已请求刷新(由调用方重新取码)。
65
+ */
66
+ async function pollUntilConfirmed(client, qrcodeHex, options) {
67
+ for (;;) {
68
+ const status = await client.getQrcodeStatus(qrcodeHex);
69
+ switch (status.state) {
70
+ case "wait":
71
+ break;
72
+ case "scaned":
73
+ options.notice("已扫码,请在微信上确认登录…");
74
+ break;
75
+ case "scaned_but_redirect": {
76
+ options.notice(`切换服务节点 ${status.baseUrl}`);
77
+ client.setBaseUrl(status.baseUrl);
78
+ break;
79
+ }
80
+ case "expired": {
81
+ const shouldRefresh = await options.onExpired();
82
+ return shouldRefresh ? null : null;
83
+ }
84
+ case "confirmed":
85
+ return status.credentials;
86
+ }
87
+ await sleep(options.pollMs);
88
+ }
89
+ }
90
+ function sleep(ms) {
91
+ return new Promise((resolve) => setTimeout(resolve, ms));
92
+ }
93
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/weixin/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Copyright 2026 harrylabsj
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /** 掩码 0–7 判定(x, y 为模块坐标)。 */
17
+ export declare function maskPattern(mask: number, x: number, y: number): boolean;
18
+ /**
19
+ * 生成 QR 码矩阵(1 = 暗模块)。版本自动选择(1–10,纠错 L),
20
+ * 8 掩码全试 penalty 最低胜出。超长(>271 字节)抛 WeixinError("validation")。
21
+ */
22
+ export declare function encodeQrMatrix(payload: string): Uint8Array;
23
+ /** 终端渲染:半块字符(00→" "、01→"▀"、10→"▄"、11→"█"),静区 4。 */
24
+ export declare function renderQrMatrix(matrix: Uint8Array, size: number, scale?: number): string[];
25
+ /** 便捷入口:payload → 渲染行(静区已含,可直接 stdout)。 */
26
+ export declare function renderQr(payload: string, scale?: number): string[];