@rezti/dsh-rez-wechat 0.1.17 → 0.1.19
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.
- package/README.md +1 -1
- package/lib/web-shim.d.ts +9 -2
- package/lib/web-shim.js +145 -11
- package/package.json +5 -5
package/README.md
CHANGED
package/lib/web-shim.d.ts
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const WECHAT_WORKSPACE_NAME = "\u5FAE\u4FE1";
|
|
11
11
|
export declare const DEFAULT_WEB_URL = "http://127.0.0.1:3080";
|
|
12
|
+
/** Written by suite when dsh web boots; carries ?token= for 0.1.5 browser auth. */
|
|
13
|
+
export declare function dshWebUrlFile(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
14
|
+
/** Origin only (no path/query). API posts go to `${origin}/api/...`. */
|
|
15
|
+
export declare function webBaseUrl(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
16
|
+
/** Launch token from env, REZ_DSH_WEB_URL query, or ~/.dsh/dsh-web.url. */
|
|
17
|
+
export declare function webLaunchToken(env?: NodeJS.ProcessEnv, home?: string): string | undefined;
|
|
18
|
+
/** Exchange launch token for the HttpOnly dsh-auth cookie (dsh 0.1.5+). */
|
|
19
|
+
export declare function mintWebAuthCookie(origin: string, token: string, timeoutMs?: number): Promise<string>;
|
|
12
20
|
/** Seeded only when the inbox folder has no AGENTS.md yet. */
|
|
13
21
|
export declare const WECHAT_INBOX_AGENTS: string;
|
|
14
22
|
export interface ModelRef {
|
|
@@ -65,7 +73,6 @@ export declare function wechatWorkspaceDir(env?: NodeJS.ProcessEnv, home?: strin
|
|
|
65
73
|
/** Write a short inbox AGENTS.md once; never overwrite a living file. Skip non-微信 rooms (企微按房间绑定). */
|
|
66
74
|
export declare function ensureWechatInboxAgents(cwd: string): void;
|
|
67
75
|
export declare function sessionStorePath(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
68
|
-
export declare function webBaseUrl(env?: NodeJS.ProcessEnv): string;
|
|
69
76
|
export declare function loadSessionStore(path: string): SessionStore;
|
|
70
77
|
export declare function saveSessionStore(path: string, store: SessionStore): void;
|
|
71
78
|
export declare function encodeClientRequest(method: string, payload: Record<string, unknown>, rpcId?: string): {
|
|
@@ -104,7 +111,7 @@ export declare function weixinBridgeEnv(opts: {
|
|
|
104
111
|
launcherPath: string;
|
|
105
112
|
shimScript: string;
|
|
106
113
|
}): NodeJS.ProcessEnv;
|
|
107
|
-
export declare function postRpc(baseUrl: string, method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
|
|
114
|
+
export declare function postRpc(baseUrl: string, method: string, params: Record<string, unknown>, timeoutMs?: number, env?: NodeJS.ProcessEnv): Promise<unknown>;
|
|
108
115
|
export declare function extractArchivedSessionIds(body: unknown): string[];
|
|
109
116
|
/** Stock dsh archives one-way and hides blank sessions. Restore + attach + title. */
|
|
110
117
|
export declare function ensureSessionVisible(rpc: RpcFn, sessionId: string, env?: NodeJS.ProcessEnv, workspaceId?: string, loopback?: boolean): Promise<void>;
|
package/lib/web-shim.js
CHANGED
|
@@ -13,6 +13,107 @@ import { homedir } from 'node:os';
|
|
|
13
13
|
import { basename, dirname, join } from 'node:path';
|
|
14
14
|
export const WECHAT_WORKSPACE_NAME = '微信';
|
|
15
15
|
export const DEFAULT_WEB_URL = 'http://127.0.0.1:3080';
|
|
16
|
+
/** Written by suite when dsh web boots; carries ?token= for 0.1.5 browser auth. */
|
|
17
|
+
export function dshWebUrlFile(env = process.env, home = homedir()) {
|
|
18
|
+
return join(dshHomeDir(env, home), 'dsh-web.url');
|
|
19
|
+
}
|
|
20
|
+
/** Origin only (no path/query). API posts go to `${origin}/api/...`. */
|
|
21
|
+
export function webBaseUrl(env = process.env, home = homedir()) {
|
|
22
|
+
const fromFile = (() => {
|
|
23
|
+
try {
|
|
24
|
+
return readFileSync(dshWebUrlFile(env, home), 'utf8').trim();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
for (const raw of [env.REZ_DSH_WEB_URL, fromFile, DEFAULT_WEB_URL]) {
|
|
31
|
+
if (raw === undefined || raw.trim() === '')
|
|
32
|
+
continue;
|
|
33
|
+
try {
|
|
34
|
+
const withScheme = /:\/\//.test(raw.trim()) ? raw.trim() : `http://${raw.trim()}`;
|
|
35
|
+
const url = new URL(withScheme);
|
|
36
|
+
return `${url.protocol}//${url.host}`;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* try next */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return DEFAULT_WEB_URL;
|
|
43
|
+
}
|
|
44
|
+
/** Launch token from env, REZ_DSH_WEB_URL query, or ~/.dsh/dsh-web.url. */
|
|
45
|
+
export function webLaunchToken(env = process.env, home = homedir()) {
|
|
46
|
+
const fromEnv = env.REZ_DSH_WEB_TOKEN?.trim();
|
|
47
|
+
if (fromEnv)
|
|
48
|
+
return fromEnv;
|
|
49
|
+
const candidates = [env.REZ_DSH_WEB_URL, (() => {
|
|
50
|
+
try {
|
|
51
|
+
return readFileSync(dshWebUrlFile(env, home), 'utf8').trim();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
})()];
|
|
57
|
+
for (const raw of candidates) {
|
|
58
|
+
if (raw === undefined || raw === '')
|
|
59
|
+
continue;
|
|
60
|
+
try {
|
|
61
|
+
const withScheme = /:\/\//.test(raw) ? raw : `http://${raw}`;
|
|
62
|
+
const token = new URL(withScheme).searchParams.get('token');
|
|
63
|
+
if (token !== null && token.length > 0)
|
|
64
|
+
return token;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* try next */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const authCookies = new Map();
|
|
73
|
+
function collectSetCookies(headers) {
|
|
74
|
+
const anyHeaders = headers;
|
|
75
|
+
if (typeof anyHeaders.getSetCookie === 'function') {
|
|
76
|
+
return anyHeaders.getSetCookie();
|
|
77
|
+
}
|
|
78
|
+
const single = headers.get('set-cookie');
|
|
79
|
+
return single === null ? [] : [single];
|
|
80
|
+
}
|
|
81
|
+
/** Exchange launch token for the HttpOnly dsh-auth cookie (dsh 0.1.5+). */
|
|
82
|
+
export async function mintWebAuthCookie(origin, token, timeoutMs = RPC_TIMEOUT_MS) {
|
|
83
|
+
const url = `${origin.replace(/\/$/, '')}/?token=${encodeURIComponent(token)}`;
|
|
84
|
+
let response;
|
|
85
|
+
try {
|
|
86
|
+
response = await fetch(url, {
|
|
87
|
+
method: 'GET',
|
|
88
|
+
redirect: 'manual',
|
|
89
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
94
|
+
throw new Error(`换取 dsh web 认证 cookie 失败(${origin}):${why}`);
|
|
95
|
+
}
|
|
96
|
+
const pairs = collectSetCookies(response.headers)
|
|
97
|
+
.map((row) => row.split(';', 1)[0]?.trim())
|
|
98
|
+
.filter((row) => typeof row === 'string' && row.includes('='));
|
|
99
|
+
if (pairs.length === 0) {
|
|
100
|
+
throw new Error('dsh web 未下发认证 cookie。请用终端打印的完整地址打开网页(必须带 ?token=),或确认 ~/.dsh/dsh-web.url 已由 suite 写入。');
|
|
101
|
+
}
|
|
102
|
+
return pairs.join('; ');
|
|
103
|
+
}
|
|
104
|
+
async function authCookieFor(origin, env, force = false) {
|
|
105
|
+
if (!force) {
|
|
106
|
+
const cached = authCookies.get(origin);
|
|
107
|
+
if (cached !== undefined)
|
|
108
|
+
return cached;
|
|
109
|
+
}
|
|
110
|
+
const token = webLaunchToken(env);
|
|
111
|
+
if (token === undefined)
|
|
112
|
+
return undefined;
|
|
113
|
+
const cookie = await mintWebAuthCookie(origin, token);
|
|
114
|
+
authCookies.set(origin, cookie);
|
|
115
|
+
return cookie;
|
|
116
|
+
}
|
|
16
117
|
/** Seeded only when the inbox folder has no AGENTS.md yet. */
|
|
17
118
|
export const WECHAT_INBOX_AGENTS = [
|
|
18
119
|
'# 微信',
|
|
@@ -151,9 +252,6 @@ export function sessionStorePath(env = process.env, home = homedir()) {
|
|
|
151
252
|
return env.REZ_WECHAT_SESSION_STORE;
|
|
152
253
|
return join(dshHomeDir(env, home), 'dsh-rez-weixin', 'web-sessions.json');
|
|
153
254
|
}
|
|
154
|
-
export function webBaseUrl(env = process.env) {
|
|
155
|
-
return (env.REZ_DSH_WEB_URL ?? DEFAULT_WEB_URL).replace(/\/$/, '');
|
|
156
|
-
}
|
|
157
255
|
export function loadSessionStore(path) {
|
|
158
256
|
try {
|
|
159
257
|
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -717,27 +815,60 @@ export function weixinBridgeEnv(opts) {
|
|
|
717
815
|
env.DSH_BRIDGE_DSH = opts.launcherPath;
|
|
718
816
|
env.REZ_WECHAT_SESSION_STORE = env.REZ_WECHAT_SESSION_STORE ?? join(opts.dataDir, 'web-sessions.json');
|
|
719
817
|
env.REZ_WECHAT_WORKSPACE = env.REZ_WECHAT_WORKSPACE ?? wechatWorkspaceDir(env);
|
|
720
|
-
|
|
818
|
+
// Prefer printed/suite URL (port + ?token=); do not force bare :3080 over it.
|
|
819
|
+
if (env.REZ_DSH_WEB_URL === undefined || env.REZ_DSH_WEB_URL.trim() === '') {
|
|
820
|
+
try {
|
|
821
|
+
const printed = readFileSync(dshWebUrlFile(env), 'utf8').trim();
|
|
822
|
+
if (printed.length > 0)
|
|
823
|
+
env.REZ_DSH_WEB_URL = printed;
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
env.REZ_DSH_WEB_URL = DEFAULT_WEB_URL;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
721
829
|
return env;
|
|
722
830
|
}
|
|
723
831
|
async function sleep(ms) {
|
|
724
832
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
725
833
|
}
|
|
726
|
-
export async function postRpc(baseUrl, method, params, timeoutMs = RPC_TIMEOUT_MS) {
|
|
727
|
-
const
|
|
834
|
+
export async function postRpc(baseUrl, method, params, timeoutMs = RPC_TIMEOUT_MS, env = process.env) {
|
|
835
|
+
const origin = (() => {
|
|
836
|
+
try {
|
|
837
|
+
const withScheme = /:\/\//.test(baseUrl) ? baseUrl : `http://${baseUrl}`;
|
|
838
|
+
const url = new URL(withScheme);
|
|
839
|
+
return `${url.protocol}//${url.host}`;
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
return webBaseUrl(env);
|
|
843
|
+
}
|
|
844
|
+
})();
|
|
845
|
+
const url = `${origin}/api/${method}`;
|
|
728
846
|
const envelope = encodeClientRequest(method, params);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
847
|
+
const send = async (cookie) => {
|
|
848
|
+
const headers = { 'content-type': 'application/json' };
|
|
849
|
+
if (cookie !== undefined && cookie.length > 0)
|
|
850
|
+
headers.cookie = cookie;
|
|
851
|
+
return fetch(url, {
|
|
732
852
|
method: 'POST',
|
|
733
|
-
headers
|
|
853
|
+
headers,
|
|
734
854
|
body: JSON.stringify(envelope),
|
|
735
855
|
signal: AbortSignal.timeout(timeoutMs),
|
|
736
856
|
});
|
|
857
|
+
};
|
|
858
|
+
let cookie = await authCookieFor(origin, env);
|
|
859
|
+
let response;
|
|
860
|
+
try {
|
|
861
|
+
response = await send(cookie);
|
|
862
|
+
if (response.status === 401) {
|
|
863
|
+
authCookies.delete(origin);
|
|
864
|
+
cookie = await authCookieFor(origin, env, true);
|
|
865
|
+
if (cookie !== undefined)
|
|
866
|
+
response = await send(cookie);
|
|
867
|
+
}
|
|
737
868
|
}
|
|
738
869
|
catch (error) {
|
|
739
870
|
const why = error instanceof Error ? error.message : String(error);
|
|
740
|
-
throw new Error(`连不上本机 dsh web(${
|
|
871
|
+
throw new Error(`连不上本机 dsh web(${origin}):${why}。请保持网页端开着。`);
|
|
741
872
|
}
|
|
742
873
|
const raw = await response.text();
|
|
743
874
|
let parsed;
|
|
@@ -745,6 +876,9 @@ export async function postRpc(baseUrl, method, params, timeoutMs = RPC_TIMEOUT_M
|
|
|
745
876
|
parsed = raw === '' ? {} : JSON.parse(raw);
|
|
746
877
|
}
|
|
747
878
|
catch {
|
|
879
|
+
if (response.status === 401) {
|
|
880
|
+
throw new Error(`dsh web ${method} 返回 401 unauthorized。请用终端打印的带 ?token= 的完整 URL 打开网页一次,或升级 suite 后确认 ~/.dsh/dsh-web.url 存在。原文: ${raw.slice(0, 160)}`);
|
|
881
|
+
}
|
|
748
882
|
throw new Error(`dsh web ${method} 返回非 JSON (${response.status}): ${raw.slice(0, 240)}`);
|
|
749
883
|
}
|
|
750
884
|
const body = unwrapRpc(parsed);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rezti/dsh-rez-wechat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "ReZ-TI WeChat/WeCom bridges. Personal WeChat is QClaw/ClawBot scan-and-chat via dsh-wechat-bridge; WeCom AI bots use the official @wecom/aibot-node-sdk (BotID + Secret) bound per Harness room.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "Apache-2.0",
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@rezti/dsh-rez-sso": "^0.1.18",
|
|
30
31
|
"@wecom/aibot-node-sdk": "^1.0.7",
|
|
31
|
-
"qrcode": "^1.5.4"
|
|
32
|
-
"@rezti/dsh-rez-sso": "0.1.14"
|
|
32
|
+
"qrcode": "^1.5.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
36
|
-
"@deepseek-ai/dsh-host-webserver": "
|
|
37
|
-
"@deepseek-ai/dsh-system-prompt": "
|
|
36
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.5-rc.2",
|
|
37
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.5-rc.2",
|
|
38
38
|
"@types/node": "^22.20.0",
|
|
39
39
|
"@types/qrcode": "^1.5.5",
|
|
40
40
|
"typescript": "~5.7.2"
|