@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 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
- return { type: 'client-request', rpcId, method, payload };
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
- const raw = Array.isArray(rec.events) ? rec.events
689
- : Array.isArray(rec.messages) ? rec.messages
690
- : Array.isArray(rec.items) ? rec.items
691
- : Array.isArray(rec.log) ? rec.log
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 url = `${origin}/api/${method}`;
846
- const envelope = encodeClientRequest(method, params);
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
- const created = await rpc('workspace.create', { path: cwd });
896
- const id = extractWorkspaceId(created) ?? findWorkspaceId(created, cwd);
897
- if (id !== undefined)
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.create accepts at most one of workspaceId / cwd.
912
- // workspaceId is the one that attachSession-s into the 微信 sidebar.
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.create', payload);
942
+ const created = await rpc('session/create', payload);
915
943
  const id = extractSessionId(created);
916
944
  if (id === undefined)
917
- throw new Error('session.create 没有返回 sessionId');
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.models', { sessionId });
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.selectModel', payload);
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('session.history', { sessionId, maxMessages: 1 });
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
- await tryRpc(rpc, 'workspace.unarchiveSession', { sessionId });
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 url = `${webBaseUrl(env)}/api/dsh-rez-suite/weixin/unarchive`;
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: { 'content-type': 'application/json' },
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.attachSession', { workspaceId, sessionId });
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.rename', { sessionId, title });
1046
+ await tryRpc(rpc, 'session/rename', { sessionId, title });
1000
1047
  }
1001
1048
  async function archiveSession(rpc, sessionId) {
1002
- await tryRpc(rpc, 'workspace.archiveSession', { sessionId });
1049
+ await tryRpc(rpc, 'workspace/archiveSession', { sessionId });
1003
1050
  }
1004
1051
  async function abortSessionTurn(rpc, sessionId) {
1005
- for (const method of ['session.abort', 'session.cancel', 'session.stop', 'session.interrupt']) {
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.respond', 'session.respondQuestion']) {
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 = historyEvents(await rpc('session.history', { sessionId, maxMessages: 40 }));
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 = historyEvents(await rpc('session.history', { sessionId, maxMessages: 40 }));
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.prompt', {
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.prompt', {
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.19",
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": {