@rezti/dsh-rez-wechat 0.1.18 → 0.1.20
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/lib/web-shim.d.ts +19 -2
- package/lib/web-shim.js +230 -57
- package/package.json +1 -1
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,9 +73,18 @@ 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;
|
|
78
|
+
/** dsh 0.1.5+ wire endpoint is `namespace/method` (slash), not `namespace.method`. */
|
|
79
|
+
export declare function normalizeRpcEndpoint(method: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Typert gateway requires `{ args }` on the wire. Unary Remotes with one
|
|
82
|
+
* `request` parameter expect `{ args: { request: fields } }`; zero-arg Remotes
|
|
83
|
+
* expect `{ args: {} }`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function wrapRemoteArgs(params: Record<string, unknown>): {
|
|
86
|
+
args: Record<string, unknown>;
|
|
87
|
+
};
|
|
71
88
|
export declare function encodeClientRequest(method: string, payload: Record<string, unknown>, rpcId?: string): {
|
|
72
89
|
type: 'client-request';
|
|
73
90
|
rpcId: string;
|
|
@@ -104,7 +121,7 @@ export declare function weixinBridgeEnv(opts: {
|
|
|
104
121
|
launcherPath: string;
|
|
105
122
|
shimScript: string;
|
|
106
123
|
}): NodeJS.ProcessEnv;
|
|
107
|
-
export declare function postRpc(baseUrl: string, method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
|
|
124
|
+
export declare function postRpc(baseUrl: string, method: string, params: Record<string, unknown>, timeoutMs?: number, env?: NodeJS.ProcessEnv): Promise<unknown>;
|
|
108
125
|
export declare function extractArchivedSessionIds(body: unknown): string[];
|
|
109
126
|
/** Stock dsh archives one-way and hides blank sessions. Restore + attach + title. */
|
|
110
127
|
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'));
|
|
@@ -172,8 +270,32 @@ export function saveSessionStore(path, store) {
|
|
|
172
270
|
mkdirSync(dirname(path), { recursive: true });
|
|
173
271
|
writeFileSync(path, JSON.stringify(store, null, 2), 'utf8');
|
|
174
272
|
}
|
|
273
|
+
/** dsh 0.1.5+ wire endpoint is `namespace/method` (slash), not `namespace.method`. */
|
|
274
|
+
export function normalizeRpcEndpoint(method) {
|
|
275
|
+
if (method.includes('/'))
|
|
276
|
+
return method;
|
|
277
|
+
const dot = method.indexOf('.');
|
|
278
|
+
if (dot <= 0)
|
|
279
|
+
return method;
|
|
280
|
+
return `${method.slice(0, dot)}/${method.slice(dot + 1)}`;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Typert gateway requires `{ args }` on the wire. Unary Remotes with one
|
|
284
|
+
* `request` parameter expect `{ args: { request: fields } }`; zero-arg Remotes
|
|
285
|
+
* expect `{ args: {} }`.
|
|
286
|
+
*/
|
|
287
|
+
export function wrapRemoteArgs(params) {
|
|
288
|
+
const keys = Object.keys(params);
|
|
289
|
+
if (keys.length === 1 && keys[0] === 'args' && typeof params.args === 'object' && params.args !== null && !Array.isArray(params.args)) {
|
|
290
|
+
return { args: params.args };
|
|
291
|
+
}
|
|
292
|
+
if (keys.length === 0)
|
|
293
|
+
return { args: {} };
|
|
294
|
+
return { args: { request: params } };
|
|
295
|
+
}
|
|
175
296
|
export function encodeClientRequest(method, payload, rpcId = randomUUID()) {
|
|
176
|
-
|
|
297
|
+
const endpoint = normalizeRpcEndpoint(method);
|
|
298
|
+
return { type: 'client-request', rpcId, method: endpoint, payload: wrapRemoteArgs(payload) };
|
|
177
299
|
}
|
|
178
300
|
export function unwrapRpc(body) {
|
|
179
301
|
if (typeof body !== 'object' || body === null)
|
|
@@ -587,11 +709,21 @@ export function historyEvents(body) {
|
|
|
587
709
|
if (typeof unwrapped !== 'object' || unwrapped === null)
|
|
588
710
|
return [];
|
|
589
711
|
const rec = unwrapped;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
712
|
+
// 0.1.5 session/page → { records: [{ type:'event', event }] }
|
|
713
|
+
const fromRecords = Array.isArray(rec.records)
|
|
714
|
+
? rec.records.map((row) => {
|
|
715
|
+
if (typeof row === 'object' && row !== null && row.type === 'event') {
|
|
716
|
+
return row.event ?? row;
|
|
717
|
+
}
|
|
718
|
+
return row;
|
|
719
|
+
})
|
|
720
|
+
: undefined;
|
|
721
|
+
const raw = fromRecords
|
|
722
|
+
?? (Array.isArray(rec.events) ? rec.events
|
|
723
|
+
: Array.isArray(rec.messages) ? rec.messages
|
|
724
|
+
: Array.isArray(rec.items) ? rec.items
|
|
725
|
+
: Array.isArray(rec.log) ? rec.log
|
|
726
|
+
: []);
|
|
595
727
|
return raw.map(unwrapHistoryItem);
|
|
596
728
|
}
|
|
597
729
|
export function lastAssistantText(events) {
|
|
@@ -717,27 +849,61 @@ export function weixinBridgeEnv(opts) {
|
|
|
717
849
|
env.DSH_BRIDGE_DSH = opts.launcherPath;
|
|
718
850
|
env.REZ_WECHAT_SESSION_STORE = env.REZ_WECHAT_SESSION_STORE ?? join(opts.dataDir, 'web-sessions.json');
|
|
719
851
|
env.REZ_WECHAT_WORKSPACE = env.REZ_WECHAT_WORKSPACE ?? wechatWorkspaceDir(env);
|
|
720
|
-
|
|
852
|
+
// Prefer printed/suite URL (port + ?token=); do not force bare :3080 over it.
|
|
853
|
+
if (env.REZ_DSH_WEB_URL === undefined || env.REZ_DSH_WEB_URL.trim() === '') {
|
|
854
|
+
try {
|
|
855
|
+
const printed = readFileSync(dshWebUrlFile(env), 'utf8').trim();
|
|
856
|
+
if (printed.length > 0)
|
|
857
|
+
env.REZ_DSH_WEB_URL = printed;
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
env.REZ_DSH_WEB_URL = DEFAULT_WEB_URL;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
721
863
|
return env;
|
|
722
864
|
}
|
|
723
865
|
async function sleep(ms) {
|
|
724
866
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
725
867
|
}
|
|
726
|
-
export async function postRpc(baseUrl, method, params, timeoutMs = RPC_TIMEOUT_MS) {
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
868
|
+
export async function postRpc(baseUrl, method, params, timeoutMs = RPC_TIMEOUT_MS, env = process.env) {
|
|
869
|
+
const origin = (() => {
|
|
870
|
+
try {
|
|
871
|
+
const withScheme = /:\/\//.test(baseUrl) ? baseUrl : `http://${baseUrl}`;
|
|
872
|
+
const url = new URL(withScheme);
|
|
873
|
+
return `${url.protocol}//${url.host}`;
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
return webBaseUrl(env);
|
|
877
|
+
}
|
|
878
|
+
})();
|
|
879
|
+
const endpoint = normalizeRpcEndpoint(method);
|
|
880
|
+
const url = `${origin}/api/${endpoint}`;
|
|
881
|
+
const envelope = encodeClientRequest(endpoint, params);
|
|
882
|
+
const send = async (cookie) => {
|
|
883
|
+
const headers = { 'content-type': 'application/json' };
|
|
884
|
+
if (cookie !== undefined && cookie.length > 0)
|
|
885
|
+
headers.cookie = cookie;
|
|
886
|
+
return fetch(url, {
|
|
732
887
|
method: 'POST',
|
|
733
|
-
headers
|
|
888
|
+
headers,
|
|
734
889
|
body: JSON.stringify(envelope),
|
|
735
890
|
signal: AbortSignal.timeout(timeoutMs),
|
|
736
891
|
});
|
|
892
|
+
};
|
|
893
|
+
let cookie = await authCookieFor(origin, env);
|
|
894
|
+
let response;
|
|
895
|
+
try {
|
|
896
|
+
response = await send(cookie);
|
|
897
|
+
if (response.status === 401) {
|
|
898
|
+
authCookies.delete(origin);
|
|
899
|
+
cookie = await authCookieFor(origin, env, true);
|
|
900
|
+
if (cookie !== undefined)
|
|
901
|
+
response = await send(cookie);
|
|
902
|
+
}
|
|
737
903
|
}
|
|
738
904
|
catch (error) {
|
|
739
905
|
const why = error instanceof Error ? error.message : String(error);
|
|
740
|
-
throw new Error(`连不上本机 dsh web(${
|
|
906
|
+
throw new Error(`连不上本机 dsh web(${origin}):${why}。请保持网页端开着。`);
|
|
741
907
|
}
|
|
742
908
|
const raw = await response.text();
|
|
743
909
|
let parsed;
|
|
@@ -745,6 +911,9 @@ export async function postRpc(baseUrl, method, params, timeoutMs = RPC_TIMEOUT_M
|
|
|
745
911
|
parsed = raw === '' ? {} : JSON.parse(raw);
|
|
746
912
|
}
|
|
747
913
|
catch {
|
|
914
|
+
if (response.status === 401) {
|
|
915
|
+
throw new Error(`dsh web ${method} 返回 401 unauthorized。请用终端打印的带 ?token= 的完整 URL 打开网页一次,或升级 suite 后确认 ~/.dsh/dsh-web.url 存在。原文: ${raw.slice(0, 160)}`);
|
|
916
|
+
}
|
|
748
917
|
throw new Error(`dsh web ${method} 返回非 JSON (${response.status}): ${raw.slice(0, 240)}`);
|
|
749
918
|
}
|
|
750
919
|
const body = unwrapRpc(parsed);
|
|
@@ -758,35 +927,28 @@ async function ensureWorkspace(rpc, cwd) {
|
|
|
758
927
|
mkdirSync(cwd, { recursive: true });
|
|
759
928
|
ensureWechatInboxAgents(cwd);
|
|
760
929
|
try {
|
|
761
|
-
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
return id;
|
|
765
|
-
}
|
|
766
|
-
catch {
|
|
767
|
-
// create may 409 / invalid-path; list is the reconnect authority
|
|
768
|
-
}
|
|
769
|
-
try {
|
|
770
|
-
return findWorkspaceId(await rpc('workspace.list', {}), cwd);
|
|
930
|
+
// 0.1.5 workspace/create is upsert-like ({ created: boolean }); list RPC is gone.
|
|
931
|
+
const created = await rpc('workspace/create', { path: cwd });
|
|
932
|
+
return extractWorkspaceId(created) ?? findWorkspaceId(created, cwd);
|
|
771
933
|
}
|
|
772
934
|
catch {
|
|
773
935
|
return undefined;
|
|
774
936
|
}
|
|
775
937
|
}
|
|
776
938
|
async function createSession(rpc, cwd, workspaceId) {
|
|
777
|
-
// Official session
|
|
778
|
-
// workspaceId is the one that
|
|
939
|
+
// Official session/create accepts at most one of workspaceId / cwd.
|
|
940
|
+
// workspaceId is the one that insertSessionBefore-s into the 微信 sidebar.
|
|
779
941
|
const payload = workspaceId !== undefined ? { workspaceId } : { cwd };
|
|
780
|
-
const created = await rpc('session
|
|
942
|
+
const created = await rpc('session/create', payload);
|
|
781
943
|
const id = extractSessionId(created);
|
|
782
944
|
if (id === undefined)
|
|
783
|
-
throw new Error('session
|
|
945
|
+
throw new Error('session/create 没有返回 sessionId');
|
|
784
946
|
return id;
|
|
785
947
|
}
|
|
786
948
|
async function maybeSelectModel(rpc, sessionId, env) {
|
|
787
949
|
let models;
|
|
788
950
|
try {
|
|
789
|
-
models = await rpc('session
|
|
951
|
+
models = await rpc('session/modelCatalog', {});
|
|
790
952
|
}
|
|
791
953
|
catch {
|
|
792
954
|
models = undefined;
|
|
@@ -796,7 +958,7 @@ async function maybeSelectModel(rpc, sessionId, env) {
|
|
|
796
958
|
return;
|
|
797
959
|
for (const payload of selectModelPayloads(sessionId, ref)) {
|
|
798
960
|
try {
|
|
799
|
-
await rpc('session
|
|
961
|
+
await rpc('session/selectModel', payload);
|
|
800
962
|
return;
|
|
801
963
|
}
|
|
802
964
|
catch {
|
|
@@ -804,15 +966,31 @@ async function maybeSelectModel(rpc, sessionId, env) {
|
|
|
804
966
|
}
|
|
805
967
|
}
|
|
806
968
|
}
|
|
969
|
+
/** 0.1.5 replaced session.history with session/page. */
|
|
970
|
+
async function fetchSessionHistory(rpc, sessionId, maxMessages = 40) {
|
|
971
|
+
return historyEvents(await rpc('session/page', {
|
|
972
|
+
address: { kind: 'session', sessionId },
|
|
973
|
+
throughSeq: Number.MAX_SAFE_INTEGER,
|
|
974
|
+
maxMessages,
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
807
977
|
async function sessionAlive(rpc, sessionId) {
|
|
808
978
|
try {
|
|
809
|
-
await rpc
|
|
979
|
+
await fetchSessionHistory(rpc, sessionId, 1);
|
|
810
980
|
return true;
|
|
811
981
|
}
|
|
812
982
|
catch {
|
|
813
983
|
return false;
|
|
814
984
|
}
|
|
815
985
|
}
|
|
986
|
+
function promptPayload(sessionId, text) {
|
|
987
|
+
return {
|
|
988
|
+
requestId: randomUUID(),
|
|
989
|
+
sessionId,
|
|
990
|
+
mode: 'queue',
|
|
991
|
+
content: [{ type: 'text', text }],
|
|
992
|
+
};
|
|
993
|
+
}
|
|
816
994
|
export function extractArchivedSessionIds(body) {
|
|
817
995
|
const unwrapped = unwrapRpc(body);
|
|
818
996
|
if (typeof unwrapped !== 'object' || unwrapped === null)
|
|
@@ -840,15 +1018,19 @@ async function tryRpc(rpc, method, params) {
|
|
|
840
1018
|
}
|
|
841
1019
|
/** Stock dsh archives one-way and hides blank sessions. Restore + attach + title. */
|
|
842
1020
|
export async function ensureSessionVisible(rpc, sessionId, env = process.env, workspaceId, loopback = true) {
|
|
843
|
-
|
|
844
|
-
await tryRpc(rpc, 'session.unarchive', { sessionId });
|
|
1021
|
+
// 0.1.5 dropped unarchive/attach RPCs; insertSessionBefore is the sidebar join.
|
|
845
1022
|
if (loopback) {
|
|
846
1023
|
const workspacePath = wechatWorkspaceDir(env);
|
|
847
|
-
const
|
|
1024
|
+
const origin = webBaseUrl(env);
|
|
1025
|
+
const url = `${origin}/api/dsh-rez-suite/weixin/unarchive`;
|
|
848
1026
|
try {
|
|
1027
|
+
const headers = { 'content-type': 'application/json' };
|
|
1028
|
+
const cookie = await authCookieFor(origin, env);
|
|
1029
|
+
if (cookie !== undefined)
|
|
1030
|
+
headers.cookie = cookie;
|
|
849
1031
|
await fetch(url, {
|
|
850
1032
|
method: 'POST',
|
|
851
|
-
headers
|
|
1033
|
+
headers,
|
|
852
1034
|
body: JSON.stringify({ sessionId, workspacePath }),
|
|
853
1035
|
signal: AbortSignal.timeout(3000),
|
|
854
1036
|
});
|
|
@@ -858,17 +1040,16 @@ export async function ensureSessionVisible(rpc, sessionId, env = process.env, wo
|
|
|
858
1040
|
}
|
|
859
1041
|
}
|
|
860
1042
|
if (workspaceId !== undefined) {
|
|
861
|
-
await tryRpc(rpc, 'workspace
|
|
862
|
-
await tryRpc(rpc, 'workspace.insertSessionBefore', { workspaceId, sessionId });
|
|
1043
|
+
await tryRpc(rpc, 'workspace/insertSessionBefore', { workspaceId, sessionId });
|
|
863
1044
|
}
|
|
864
1045
|
const title = basename(wechatWorkspaceDir(env)) || WECHAT_WORKSPACE_NAME;
|
|
865
|
-
await tryRpc(rpc, 'session
|
|
1046
|
+
await tryRpc(rpc, 'session/rename', { sessionId, title });
|
|
866
1047
|
}
|
|
867
1048
|
async function archiveSession(rpc, sessionId) {
|
|
868
|
-
await tryRpc(rpc, 'workspace
|
|
1049
|
+
await tryRpc(rpc, 'workspace/archiveSession', { sessionId });
|
|
869
1050
|
}
|
|
870
1051
|
async function abortSessionTurn(rpc, sessionId) {
|
|
871
|
-
for (const method of ['session
|
|
1052
|
+
for (const method of ['session/cancel', 'session/abort', 'session/stop', 'session/interrupt']) {
|
|
872
1053
|
if (await tryRpc(rpc, method, { sessionId }))
|
|
873
1054
|
return;
|
|
874
1055
|
}
|
|
@@ -898,7 +1079,7 @@ async function tryAnswerPendingQuestion(rpc, sessionId, choice) {
|
|
|
898
1079
|
{ sessionId, answer },
|
|
899
1080
|
{ sessionId, answers: answer.answers },
|
|
900
1081
|
];
|
|
901
|
-
for (const method of ['question
|
|
1082
|
+
for (const method of ['question/respond', 'session/respondQuestion']) {
|
|
902
1083
|
for (const params of payloads) {
|
|
903
1084
|
if (await tryRpcQuick(rpc, method, params))
|
|
904
1085
|
return true;
|
|
@@ -923,7 +1104,7 @@ async function waitForAssistant(rpc, sessionId, beforeText, timeoutMs, ignorePen
|
|
|
923
1104
|
while (Date.now() < deadline) {
|
|
924
1105
|
let events = [];
|
|
925
1106
|
try {
|
|
926
|
-
events =
|
|
1107
|
+
events = await fetchSessionHistory(rpc, sessionId, 40);
|
|
927
1108
|
}
|
|
928
1109
|
catch {
|
|
929
1110
|
events = [];
|
|
@@ -994,7 +1175,7 @@ export async function runHeadlessViaWeb(opts) {
|
|
|
994
1175
|
let before = '';
|
|
995
1176
|
let events = [];
|
|
996
1177
|
try {
|
|
997
|
-
events =
|
|
1178
|
+
events = await fetchSessionHistory(rpc, sessionId, 40);
|
|
998
1179
|
before = usableAssistantText(lastAssistantText(events));
|
|
999
1180
|
}
|
|
1000
1181
|
catch {
|
|
@@ -1013,11 +1194,7 @@ export async function runHeadlessViaWeb(opts) {
|
|
|
1013
1194
|
const answered = await tryAnswerPendingQuestion(rpc, sessionId, choice);
|
|
1014
1195
|
if (!answered) {
|
|
1015
1196
|
await abortSessionTurn(rpc, sessionId);
|
|
1016
|
-
await rpc('session
|
|
1017
|
-
sessionId,
|
|
1018
|
-
mode: 'queue',
|
|
1019
|
-
content: [{ type: 'text', text: `用户在微信选择了:${choice.label}` }],
|
|
1020
|
-
});
|
|
1197
|
+
await rpc('session/prompt', promptPayload(sessionId, `用户在微信选择了:${choice.label}`));
|
|
1021
1198
|
}
|
|
1022
1199
|
skipUserPrompt = true;
|
|
1023
1200
|
}
|
|
@@ -1026,11 +1203,7 @@ export async function runHeadlessViaWeb(opts) {
|
|
|
1026
1203
|
if (!turnIsIdle(events) || turnIsBlocked(events)) {
|
|
1027
1204
|
await abortSessionTurn(rpc, sessionId);
|
|
1028
1205
|
}
|
|
1029
|
-
await rpc('session
|
|
1030
|
-
sessionId,
|
|
1031
|
-
mode: 'queue',
|
|
1032
|
-
content: [{ type: 'text', text: promptText }],
|
|
1033
|
-
});
|
|
1206
|
+
await rpc('session/prompt', promptPayload(sessionId, promptText));
|
|
1034
1207
|
}
|
|
1035
1208
|
const reply = await waitForAssistant(rpc, sessionId, before, timeoutMs, questions, stuckAck(folder));
|
|
1036
1209
|
store.sessions[key] = {
|
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.20",
|
|
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": {
|