@rezti/dsh-rez-wechat 0.1.19 → 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 +10 -0
- package/lib/web-shim.js +86 -47
- package/package.json +1 -1
package/lib/web-shim.d.ts
CHANGED
|
@@ -75,6 +75,16 @@ export declare function ensureWechatInboxAgents(cwd: string): void;
|
|
|
75
75
|
export declare function sessionStorePath(env?: NodeJS.ProcessEnv, home?: string): string;
|
|
76
76
|
export declare function loadSessionStore(path: string): SessionStore;
|
|
77
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
|
+
};
|
|
78
88
|
export declare function encodeClientRequest(method: string, payload: Record<string, unknown>, rpcId?: string): {
|
|
79
89
|
type: 'client-request';
|
|
80
90
|
rpcId: string;
|
package/lib/web-shim.js
CHANGED
|
@@ -270,8 +270,32 @@ export function saveSessionStore(path, store) {
|
|
|
270
270
|
mkdirSync(dirname(path), { recursive: true });
|
|
271
271
|
writeFileSync(path, JSON.stringify(store, null, 2), 'utf8');
|
|
272
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
|
+
}
|
|
273
296
|
export function encodeClientRequest(method, payload, rpcId = randomUUID()) {
|
|
274
|
-
|
|
297
|
+
const endpoint = normalizeRpcEndpoint(method);
|
|
298
|
+
return { type: 'client-request', rpcId, method: endpoint, payload: wrapRemoteArgs(payload) };
|
|
275
299
|
}
|
|
276
300
|
export function unwrapRpc(body) {
|
|
277
301
|
if (typeof body !== 'object' || body === null)
|
|
@@ -685,11 +709,21 @@ export function historyEvents(body) {
|
|
|
685
709
|
if (typeof unwrapped !== 'object' || unwrapped === null)
|
|
686
710
|
return [];
|
|
687
711
|
const rec = unwrapped;
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
+
: []);
|
|
693
727
|
return raw.map(unwrapHistoryItem);
|
|
694
728
|
}
|
|
695
729
|
export function lastAssistantText(events) {
|
|
@@ -842,8 +876,9 @@ export async function postRpc(baseUrl, method, params, timeoutMs = RPC_TIMEOUT_M
|
|
|
842
876
|
return webBaseUrl(env);
|
|
843
877
|
}
|
|
844
878
|
})();
|
|
845
|
-
const
|
|
846
|
-
const
|
|
879
|
+
const endpoint = normalizeRpcEndpoint(method);
|
|
880
|
+
const url = `${origin}/api/${endpoint}`;
|
|
881
|
+
const envelope = encodeClientRequest(endpoint, params);
|
|
847
882
|
const send = async (cookie) => {
|
|
848
883
|
const headers = { 'content-type': 'application/json' };
|
|
849
884
|
if (cookie !== undefined && cookie.length > 0)
|
|
@@ -892,35 +927,28 @@ async function ensureWorkspace(rpc, cwd) {
|
|
|
892
927
|
mkdirSync(cwd, { recursive: true });
|
|
893
928
|
ensureWechatInboxAgents(cwd);
|
|
894
929
|
try {
|
|
895
|
-
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
return id;
|
|
899
|
-
}
|
|
900
|
-
catch {
|
|
901
|
-
// create may 409 / invalid-path; list is the reconnect authority
|
|
902
|
-
}
|
|
903
|
-
try {
|
|
904
|
-
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);
|
|
905
933
|
}
|
|
906
934
|
catch {
|
|
907
935
|
return undefined;
|
|
908
936
|
}
|
|
909
937
|
}
|
|
910
938
|
async function createSession(rpc, cwd, workspaceId) {
|
|
911
|
-
// Official session
|
|
912
|
-
// 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.
|
|
913
941
|
const payload = workspaceId !== undefined ? { workspaceId } : { cwd };
|
|
914
|
-
const created = await rpc('session
|
|
942
|
+
const created = await rpc('session/create', payload);
|
|
915
943
|
const id = extractSessionId(created);
|
|
916
944
|
if (id === undefined)
|
|
917
|
-
throw new Error('session
|
|
945
|
+
throw new Error('session/create 没有返回 sessionId');
|
|
918
946
|
return id;
|
|
919
947
|
}
|
|
920
948
|
async function maybeSelectModel(rpc, sessionId, env) {
|
|
921
949
|
let models;
|
|
922
950
|
try {
|
|
923
|
-
models = await rpc('session
|
|
951
|
+
models = await rpc('session/modelCatalog', {});
|
|
924
952
|
}
|
|
925
953
|
catch {
|
|
926
954
|
models = undefined;
|
|
@@ -930,7 +958,7 @@ async function maybeSelectModel(rpc, sessionId, env) {
|
|
|
930
958
|
return;
|
|
931
959
|
for (const payload of selectModelPayloads(sessionId, ref)) {
|
|
932
960
|
try {
|
|
933
|
-
await rpc('session
|
|
961
|
+
await rpc('session/selectModel', payload);
|
|
934
962
|
return;
|
|
935
963
|
}
|
|
936
964
|
catch {
|
|
@@ -938,15 +966,31 @@ async function maybeSelectModel(rpc, sessionId, env) {
|
|
|
938
966
|
}
|
|
939
967
|
}
|
|
940
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
|
+
}
|
|
941
977
|
async function sessionAlive(rpc, sessionId) {
|
|
942
978
|
try {
|
|
943
|
-
await rpc
|
|
979
|
+
await fetchSessionHistory(rpc, sessionId, 1);
|
|
944
980
|
return true;
|
|
945
981
|
}
|
|
946
982
|
catch {
|
|
947
983
|
return false;
|
|
948
984
|
}
|
|
949
985
|
}
|
|
986
|
+
function promptPayload(sessionId, text) {
|
|
987
|
+
return {
|
|
988
|
+
requestId: randomUUID(),
|
|
989
|
+
sessionId,
|
|
990
|
+
mode: 'queue',
|
|
991
|
+
content: [{ type: 'text', text }],
|
|
992
|
+
};
|
|
993
|
+
}
|
|
950
994
|
export function extractArchivedSessionIds(body) {
|
|
951
995
|
const unwrapped = unwrapRpc(body);
|
|
952
996
|
if (typeof unwrapped !== 'object' || unwrapped === null)
|
|
@@ -974,15 +1018,19 @@ async function tryRpc(rpc, method, params) {
|
|
|
974
1018
|
}
|
|
975
1019
|
/** Stock dsh archives one-way and hides blank sessions. Restore + attach + title. */
|
|
976
1020
|
export async function ensureSessionVisible(rpc, sessionId, env = process.env, workspaceId, loopback = true) {
|
|
977
|
-
|
|
978
|
-
await tryRpc(rpc, 'session.unarchive', { sessionId });
|
|
1021
|
+
// 0.1.5 dropped unarchive/attach RPCs; insertSessionBefore is the sidebar join.
|
|
979
1022
|
if (loopback) {
|
|
980
1023
|
const workspacePath = wechatWorkspaceDir(env);
|
|
981
|
-
const
|
|
1024
|
+
const origin = webBaseUrl(env);
|
|
1025
|
+
const url = `${origin}/api/dsh-rez-suite/weixin/unarchive`;
|
|
982
1026
|
try {
|
|
1027
|
+
const headers = { 'content-type': 'application/json' };
|
|
1028
|
+
const cookie = await authCookieFor(origin, env);
|
|
1029
|
+
if (cookie !== undefined)
|
|
1030
|
+
headers.cookie = cookie;
|
|
983
1031
|
await fetch(url, {
|
|
984
1032
|
method: 'POST',
|
|
985
|
-
headers
|
|
1033
|
+
headers,
|
|
986
1034
|
body: JSON.stringify({ sessionId, workspacePath }),
|
|
987
1035
|
signal: AbortSignal.timeout(3000),
|
|
988
1036
|
});
|
|
@@ -992,17 +1040,16 @@ export async function ensureSessionVisible(rpc, sessionId, env = process.env, wo
|
|
|
992
1040
|
}
|
|
993
1041
|
}
|
|
994
1042
|
if (workspaceId !== undefined) {
|
|
995
|
-
await tryRpc(rpc, 'workspace
|
|
996
|
-
await tryRpc(rpc, 'workspace.insertSessionBefore', { workspaceId, sessionId });
|
|
1043
|
+
await tryRpc(rpc, 'workspace/insertSessionBefore', { workspaceId, sessionId });
|
|
997
1044
|
}
|
|
998
1045
|
const title = basename(wechatWorkspaceDir(env)) || WECHAT_WORKSPACE_NAME;
|
|
999
|
-
await tryRpc(rpc, 'session
|
|
1046
|
+
await tryRpc(rpc, 'session/rename', { sessionId, title });
|
|
1000
1047
|
}
|
|
1001
1048
|
async function archiveSession(rpc, sessionId) {
|
|
1002
|
-
await tryRpc(rpc, 'workspace
|
|
1049
|
+
await tryRpc(rpc, 'workspace/archiveSession', { sessionId });
|
|
1003
1050
|
}
|
|
1004
1051
|
async function abortSessionTurn(rpc, sessionId) {
|
|
1005
|
-
for (const method of ['session
|
|
1052
|
+
for (const method of ['session/cancel', 'session/abort', 'session/stop', 'session/interrupt']) {
|
|
1006
1053
|
if (await tryRpc(rpc, method, { sessionId }))
|
|
1007
1054
|
return;
|
|
1008
1055
|
}
|
|
@@ -1032,7 +1079,7 @@ async function tryAnswerPendingQuestion(rpc, sessionId, choice) {
|
|
|
1032
1079
|
{ sessionId, answer },
|
|
1033
1080
|
{ sessionId, answers: answer.answers },
|
|
1034
1081
|
];
|
|
1035
|
-
for (const method of ['question
|
|
1082
|
+
for (const method of ['question/respond', 'session/respondQuestion']) {
|
|
1036
1083
|
for (const params of payloads) {
|
|
1037
1084
|
if (await tryRpcQuick(rpc, method, params))
|
|
1038
1085
|
return true;
|
|
@@ -1057,7 +1104,7 @@ async function waitForAssistant(rpc, sessionId, beforeText, timeoutMs, ignorePen
|
|
|
1057
1104
|
while (Date.now() < deadline) {
|
|
1058
1105
|
let events = [];
|
|
1059
1106
|
try {
|
|
1060
|
-
events =
|
|
1107
|
+
events = await fetchSessionHistory(rpc, sessionId, 40);
|
|
1061
1108
|
}
|
|
1062
1109
|
catch {
|
|
1063
1110
|
events = [];
|
|
@@ -1128,7 +1175,7 @@ export async function runHeadlessViaWeb(opts) {
|
|
|
1128
1175
|
let before = '';
|
|
1129
1176
|
let events = [];
|
|
1130
1177
|
try {
|
|
1131
|
-
events =
|
|
1178
|
+
events = await fetchSessionHistory(rpc, sessionId, 40);
|
|
1132
1179
|
before = usableAssistantText(lastAssistantText(events));
|
|
1133
1180
|
}
|
|
1134
1181
|
catch {
|
|
@@ -1147,11 +1194,7 @@ export async function runHeadlessViaWeb(opts) {
|
|
|
1147
1194
|
const answered = await tryAnswerPendingQuestion(rpc, sessionId, choice);
|
|
1148
1195
|
if (!answered) {
|
|
1149
1196
|
await abortSessionTurn(rpc, sessionId);
|
|
1150
|
-
await rpc('session
|
|
1151
|
-
sessionId,
|
|
1152
|
-
mode: 'queue',
|
|
1153
|
-
content: [{ type: 'text', text: `用户在微信选择了:${choice.label}` }],
|
|
1154
|
-
});
|
|
1197
|
+
await rpc('session/prompt', promptPayload(sessionId, `用户在微信选择了:${choice.label}`));
|
|
1155
1198
|
}
|
|
1156
1199
|
skipUserPrompt = true;
|
|
1157
1200
|
}
|
|
@@ -1160,11 +1203,7 @@ export async function runHeadlessViaWeb(opts) {
|
|
|
1160
1203
|
if (!turnIsIdle(events) || turnIsBlocked(events)) {
|
|
1161
1204
|
await abortSessionTurn(rpc, sessionId);
|
|
1162
1205
|
}
|
|
1163
|
-
await rpc('session
|
|
1164
|
-
sessionId,
|
|
1165
|
-
mode: 'queue',
|
|
1166
|
-
content: [{ type: 'text', text: promptText }],
|
|
1167
|
-
});
|
|
1206
|
+
await rpc('session/prompt', promptPayload(sessionId, promptText));
|
|
1168
1207
|
}
|
|
1169
1208
|
const reply = await waitForAssistant(rpc, sessionId, before, timeoutMs, questions, stuckAck(folder));
|
|
1170
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": {
|