@yanhaidao/wecom 2.3.4 → 2.3.10

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 (110) hide show
  1. package/README.md +222 -335
  2. package/assets/03.bot.page.png +0 -0
  3. package/changelog/v2.3.10.md +17 -0
  4. package/changelog/v2.3.9.md +22 -0
  5. package/compat-single-account.md +34 -4
  6. package/index.ts +5 -5
  7. package/package.json +8 -7
  8. package/src/agent/api-client.upload.test.ts +1 -2
  9. package/src/agent/handler.ts +82 -9
  10. package/src/agent/index.ts +1 -1
  11. package/src/app/account-runtime.ts +245 -0
  12. package/src/app/bootstrap.ts +29 -0
  13. package/src/app/index.ts +31 -0
  14. package/src/capability/agent/delivery-service.ts +79 -0
  15. package/src/capability/agent/fallback-policy.ts +13 -0
  16. package/src/capability/agent/index.ts +3 -0
  17. package/src/capability/agent/ingress-service.ts +38 -0
  18. package/src/capability/bot/dispatch-config.ts +47 -0
  19. package/src/capability/bot/fallback-delivery.ts +178 -0
  20. package/src/capability/bot/index.ts +1 -0
  21. package/src/capability/bot/local-path-delivery.ts +215 -0
  22. package/src/capability/bot/service.ts +56 -0
  23. package/src/capability/bot/stream-delivery.ts +379 -0
  24. package/src/capability/bot/stream-finalizer.ts +120 -0
  25. package/src/capability/bot/stream-orchestrator.ts +352 -0
  26. package/src/capability/bot/types.ts +8 -0
  27. package/src/capability/index.ts +2 -0
  28. package/src/channel.lifecycle.test.ts +9 -6
  29. package/src/channel.meta.test.ts +12 -0
  30. package/src/channel.ts +48 -21
  31. package/src/config/accounts.resolve.test.ts +39 -2
  32. package/src/config/accounts.ts +242 -280
  33. package/src/config/derived-paths.test.ts +111 -0
  34. package/src/config/derived-paths.ts +41 -0
  35. package/src/config/index.ts +10 -12
  36. package/src/config/runtime-config.ts +46 -0
  37. package/src/config/schema.ts +65 -103
  38. package/src/domain/models.ts +7 -0
  39. package/src/domain/policies.ts +36 -0
  40. package/src/dynamic-agent.ts +6 -0
  41. package/src/gateway-monitor.ts +43 -93
  42. package/src/http.ts +23 -2
  43. package/src/monitor/limits.ts +7 -0
  44. package/src/monitor/state.ts +28 -508
  45. package/src/monitor.active.test.ts +3 -3
  46. package/src/monitor.integration.test.ts +0 -1
  47. package/src/monitor.ts +64 -2603
  48. package/src/monitor.webhook.test.ts +127 -42
  49. package/src/observability/audit-log.ts +48 -0
  50. package/src/observability/legacy-operational-event-store.ts +36 -0
  51. package/src/observability/raw-envelope-log.ts +28 -0
  52. package/src/observability/status-registry.ts +13 -0
  53. package/src/observability/transport-session-view.ts +14 -0
  54. package/src/onboarding.test.ts +268 -0
  55. package/src/onboarding.ts +95 -78
  56. package/src/outbound.test.ts +5 -5
  57. package/src/outbound.ts +18 -66
  58. package/src/runtime/dispatcher.ts +52 -0
  59. package/src/runtime/index.ts +4 -0
  60. package/src/runtime/outbound-intent.ts +4 -0
  61. package/src/runtime/reply-orchestrator.test.ts +38 -0
  62. package/src/runtime/reply-orchestrator.ts +55 -0
  63. package/src/runtime/routing-bridge.ts +19 -0
  64. package/src/runtime/session-manager.ts +76 -0
  65. package/src/runtime.ts +7 -14
  66. package/src/shared/command-auth.ts +1 -17
  67. package/src/shared/media-service.ts +36 -0
  68. package/src/shared/media-types.ts +5 -0
  69. package/src/store/active-reply-store.ts +42 -0
  70. package/src/store/interfaces.ts +11 -0
  71. package/src/store/memory-store.ts +43 -0
  72. package/src/store/stream-batch-store.ts +350 -0
  73. package/src/target.ts +28 -0
  74. package/src/transport/agent-api/client.ts +44 -0
  75. package/src/transport/agent-api/core.ts +367 -0
  76. package/src/transport/agent-api/delivery.ts +41 -0
  77. package/src/transport/agent-api/media-upload.ts +11 -0
  78. package/src/transport/agent-api/reply.ts +39 -0
  79. package/src/transport/agent-callback/http-handler.ts +47 -0
  80. package/src/transport/agent-callback/inbound.ts +5 -0
  81. package/src/transport/agent-callback/reply.ts +13 -0
  82. package/src/transport/agent-callback/request-handler.ts +244 -0
  83. package/src/transport/agent-callback/session.ts +23 -0
  84. package/src/transport/bot-webhook/active-reply.ts +36 -0
  85. package/src/transport/bot-webhook/http-handler.ts +48 -0
  86. package/src/transport/bot-webhook/inbound-normalizer.ts +371 -0
  87. package/src/transport/bot-webhook/inbound.ts +5 -0
  88. package/src/transport/bot-webhook/message-shape.ts +89 -0
  89. package/src/transport/bot-webhook/protocol.ts +148 -0
  90. package/src/transport/bot-webhook/reply.ts +15 -0
  91. package/src/transport/bot-webhook/request-handler.ts +394 -0
  92. package/src/transport/bot-webhook/session.ts +23 -0
  93. package/src/transport/bot-ws/inbound.ts +108 -0
  94. package/src/transport/bot-ws/reply.ts +63 -0
  95. package/src/transport/bot-ws/sdk-adapter.ts +180 -0
  96. package/src/transport/bot-ws/session.ts +28 -0
  97. package/src/transport/http/common.ts +109 -0
  98. package/src/transport/http/registry.ts +92 -0
  99. package/src/transport/http/request-handler.ts +84 -0
  100. package/src/transport/index.ts +14 -0
  101. package/src/types/account.ts +56 -91
  102. package/src/types/config.ts +64 -112
  103. package/src/types/constants.ts +20 -35
  104. package/src/types/events.ts +21 -0
  105. package/src/types/index.ts +14 -38
  106. package/src/types/legacy-stream.ts +50 -0
  107. package/src/types/runtime-context.ts +28 -0
  108. package/src/types/runtime.ts +161 -0
  109. package/src/agent/api-client.ts +0 -383
  110. package/src/monitor/types.ts +0 -136
@@ -1,18 +1,20 @@
1
1
  import type {
2
2
  ChannelGatewayContext,
3
3
  OpenClawConfig,
4
- PluginRuntime,
5
4
  } from "openclaw/plugin-sdk";
6
5
 
7
6
  import {
8
- detectMode,
9
7
  listWecomAccountIds,
8
+ resolveDerivedPathSummary,
10
9
  resolveWecomAccount,
11
10
  resolveWecomAccountConflict,
12
11
  } from "./config/index.js";
13
- import { registerAgentWebhookTarget, registerWecomWebhookTarget } from "./monitor.js";
12
+ import { createAccountRuntime } from "./app/bootstrap.js";
13
+ import { registerAccountRuntime, unregisterAccountRuntime } from "./app/index.js";
14
14
  import type { ResolvedWecomAccount, WecomConfig } from "./types/index.js";
15
- import { WEBHOOK_PATHS } from "./types/constants.js";
15
+ import { WecomBotCapabilityService } from "./capability/bot/index.js";
16
+ import { WecomAgentIngressService } from "./capability/agent/index.js";
17
+ import type { WecomRuntimeEnv } from "./types/runtime-context.js";
16
18
 
17
19
  type AccountRouteRegistryItem = {
18
20
  botPaths: string[];
@@ -75,30 +77,6 @@ function waitForAbortSignal(abortSignal: AbortSignal): Promise<void> {
75
77
  });
76
78
  }
77
79
 
78
- function uniquePaths(paths: string[]): string[] {
79
- return Array.from(new Set(paths.map((path) => path.trim()).filter(Boolean)));
80
- }
81
-
82
- function resolveBotRegistrationPaths(params: { accountId: string; matrixMode: boolean }): string[] {
83
- if (params.matrixMode) {
84
- return uniquePaths([
85
- `${WEBHOOK_PATHS.BOT_PLUGIN}/${params.accountId}`,
86
- `${WEBHOOK_PATHS.BOT_ALT}/${params.accountId}`,
87
- ]);
88
- }
89
- return uniquePaths([WEBHOOK_PATHS.BOT_PLUGIN, WEBHOOK_PATHS.BOT, WEBHOOK_PATHS.BOT_ALT]);
90
- }
91
-
92
- function resolveAgentRegistrationPaths(params: { accountId: string; matrixMode: boolean }): string[] {
93
- if (params.matrixMode) {
94
- return uniquePaths([
95
- `${WEBHOOK_PATHS.AGENT_PLUGIN}/${params.accountId}`,
96
- `${WEBHOOK_PATHS.AGENT}/${params.accountId}`,
97
- ]);
98
- }
99
- return uniquePaths([WEBHOOK_PATHS.AGENT_PLUGIN, WEBHOOK_PATHS.AGENT]);
100
- }
101
-
102
80
  /**
103
81
  * Keeps WeCom webhook targets registered for the account lifecycle.
104
82
  * The promise only settles after gateway abort/reload signals shutdown.
@@ -122,29 +100,11 @@ export async function monitorWecomProvider(
122
100
  });
123
101
  throw new Error(conflict.message);
124
102
  }
125
- const mode = detectMode(cfg.channels?.wecom as WecomConfig | undefined);
126
- const matrixMode = mode === "matrix";
127
103
  const bot = account.bot;
128
104
  const agent = account.agent;
129
105
  const botConfigured = Boolean(bot?.configured);
130
106
  const agentConfigured = Boolean(agent?.configured);
131
107
 
132
- if (mode === "legacy" && (botConfigured || agentConfigured)) {
133
- if (agentConfigured && !botConfigured) {
134
- ctx.log?.warn(
135
- `[${account.accountId}] 检测到仍在使用单 Agent 兼容模式。建议尽快升级为多账号模式:` +
136
- `将 channels.wecom.agent 迁移到 channels.wecom.accounts.<accountId>.agent,` +
137
- `并设置 channels.wecom.defaultAccount。`,
138
- );
139
- } else {
140
- ctx.log?.warn(
141
- `[${account.accountId}] 检测到仍在使用单账号兼容模式。建议尽快升级为多账号模式:` +
142
- `将 channels.wecom.bot/agent 迁移到 channels.wecom.accounts.<accountId>.bot/agent,` +
143
- `并设置 channels.wecom.defaultAccount。`,
144
- );
145
- }
146
- }
147
-
148
108
  if (!botConfigured && !agentConfigured) {
149
109
  ctx.log?.warn(`[${account.accountId}] wecom not configured; channel is idle`);
150
110
  ctx.setStatus({ accountId: account.accountId, running: false, configured: false });
@@ -152,50 +112,38 @@ export async function monitorWecomProvider(
152
112
  return;
153
113
  }
154
114
 
155
- const unregisters: Array<() => void> = [];
115
+ const accountRuntime = createAccountRuntime(ctx);
116
+ registerAccountRuntime(accountRuntime);
156
117
  const botPaths: string[] = [];
157
118
  const agentPaths: string[] = [];
119
+ const runtimeEnv: WecomRuntimeEnv = {
120
+ log: (message) => ctx.log?.info(message),
121
+ error: (message) => ctx.log?.error(message),
122
+ };
123
+ const botService = new WecomBotCapabilityService(
124
+ accountRuntime,
125
+ cfg,
126
+ runtimeEnv,
127
+ );
128
+ const agentIngress = new WecomAgentIngressService(accountRuntime, cfg, runtimeEnv);
158
129
  try {
159
- if (bot && botConfigured) {
160
- const paths = resolveBotRegistrationPaths({
161
- accountId: account.accountId,
162
- matrixMode,
163
- });
164
- for (const path of paths) {
165
- unregisters.push(
166
- registerWecomWebhookTarget({
167
- account: bot,
168
- config: cfg,
169
- runtime: ctx.runtime,
170
- // The HTTP handler resolves the active PluginRuntime via getWecomRuntime().
171
- // The stored target only needs to be decrypt/verify-capable.
172
- core: {} as PluginRuntime,
173
- path,
174
- statusSink: (patch) => ctx.setStatus({ accountId: ctx.accountId, ...patch }),
175
- }),
176
- );
177
- }
178
- botPaths.push(...paths);
179
- ctx.log?.info(`[${account.accountId}] wecom bot webhook registered at ${paths.join(", ")}`);
130
+ ctx.log?.info(
131
+ `[${account.accountId}] wecom runtime start bot=${bot?.primaryTransport ?? "disabled"} agent=${agentConfigured ? "callback/api" : "disabled"}`,
132
+ );
133
+ const botRegistration = botService.start();
134
+ if (botRegistration) {
135
+ botPaths.push(...botRegistration.descriptors);
136
+ ctx.log?.info(
137
+ `[${account.accountId}] wecom bot ${botRegistration.transport} started: ${botRegistration.descriptors.join(", ")}`,
138
+ );
180
139
  }
181
140
 
182
- if (agent && agentConfigured) {
183
- const paths = resolveAgentRegistrationPaths({
184
- accountId: account.accountId,
185
- matrixMode,
186
- });
187
- for (const path of paths) {
188
- unregisters.push(
189
- registerAgentWebhookTarget({
190
- agent,
191
- config: cfg,
192
- runtime: ctx.runtime,
193
- path,
194
- }),
195
- );
196
- }
197
- agentPaths.push(...paths);
198
- ctx.log?.info(`[${account.accountId}] wecom agent webhook registered at ${paths.join(", ")}`);
141
+ const agentRegistration = agentIngress.start();
142
+ if (agentRegistration) {
143
+ agentPaths.push(...agentRegistration.descriptors);
144
+ ctx.log?.info(
145
+ `[${account.accountId}] wecom agent ${agentRegistration.transport} started: ${agentRegistration.descriptors.join(", ")}`,
146
+ );
199
147
  }
200
148
 
201
149
  accountRouteRegistry.set(account.accountId, { botPaths, agentPaths });
@@ -207,25 +155,27 @@ export async function monitorWecomProvider(
207
155
  }
208
156
 
209
157
  ctx.setStatus({
210
- accountId: account.accountId,
211
158
  running: true,
212
159
  configured: true,
213
- webhookPath: botConfigured
214
- ? (botPaths[0] ?? WEBHOOK_PATHS.BOT_PLUGIN)
215
- : (agentPaths[0] ?? WEBHOOK_PATHS.AGENT_PLUGIN),
160
+ webhookPath: botPaths[0] ?? agentPaths[0] ?? null,
216
161
  lastStartAt: Date.now(),
162
+ ...accountRuntime.buildRuntimeStatus(),
217
163
  });
164
+ ctx.log?.info(
165
+ `[${account.accountId}] runtime status health=${accountRuntime.buildRuntimeStatus().health} transports=${(accountRuntime.buildRuntimeStatus().transportSessions ?? []).join(" | ") || "none"}`,
166
+ );
218
167
 
219
168
  await waitForAbortSignal(ctx.abortSignal);
220
169
  } finally {
221
- for (const unregister of unregisters) {
222
- unregister();
223
- }
170
+ botService.stop();
171
+ agentIngress.stop();
224
172
  accountRouteRegistry.delete(account.accountId);
173
+ unregisterAccountRuntime(account.accountId);
225
174
  ctx.setStatus({
226
- accountId: account.accountId,
227
175
  running: false,
228
176
  lastStopAt: Date.now(),
177
+ ...accountRuntime.buildRuntimeStatus(),
229
178
  });
179
+ ctx.log?.info(`[${account.accountId}] wecom runtime stopped`);
230
180
  }
231
181
  }
package/src/http.ts CHANGED
@@ -18,6 +18,15 @@ function getProxyDispatcher(proxyUrl: string): ProxyDispatcher {
18
18
  return created;
19
19
  }
20
20
 
21
+ function summarizeHttpTarget(input: string | URL): string {
22
+ try {
23
+ const url = typeof input === "string" ? new URL(input) : input;
24
+ return `${url.origin}${url.pathname}`;
25
+ } catch {
26
+ return String(input);
27
+ }
28
+ }
29
+
21
30
  function mergeAbortSignal(params: {
22
31
  signal?: AbortSignal;
23
32
  timeoutMs?: number;
@@ -54,6 +63,9 @@ export type WecomHttpOptions = {
54
63
  export async function wecomFetch(input: string | URL, init?: RequestInit, opts?: WecomHttpOptions): Promise<Response> {
55
64
  const proxyUrl = opts?.proxyUrl?.trim() ?? "";
56
65
  const dispatcher = proxyUrl ? getProxyDispatcher(proxyUrl) : undefined;
66
+ const startedAt = Date.now();
67
+ const method = (init?.method ?? "GET").toUpperCase();
68
+ const target = summarizeHttpTarget(input);
57
69
 
58
70
  const initSignal = init?.signal ?? undefined;
59
71
  const signal = mergeAbortSignal({ signal: opts?.signal ?? initSignal, timeoutMs: opts?.timeoutMs });
@@ -71,11 +83,20 @@ export async function wecomFetch(input: string | URL, init?: RequestInit, opts?:
71
83
  };
72
84
 
73
85
  try {
74
- return await undiciFetch(input, nextInit as Parameters<typeof undiciFetch>[1]) as unknown as Response;
86
+ console.log(
87
+ `[wecom-http] request method=${method} target=${target} proxy=${proxyUrl || "none"} timeoutMs=${String(opts?.timeoutMs ?? "none")}`,
88
+ );
89
+ const response = await undiciFetch(input, nextInit as Parameters<typeof undiciFetch>[1]) as unknown as Response;
90
+ console.log(
91
+ `[wecom-http] response method=${method} target=${target} status=${response.status} durationMs=${Date.now() - startedAt}`,
92
+ );
93
+ return response;
75
94
  } catch (err: unknown) {
76
95
  if (err instanceof Error && err.name === "TypeError" && err.message === "fetch failed") {
77
96
  const cause = (err as any).cause;
78
- console.error(`[wecom-http] fetch failed: ${input} (proxy: ${proxyUrl || "none"})${cause ? ` - cause: ${String(cause)}` : ""}`);
97
+ console.error(
98
+ `[wecom-http] fetch failed method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}${cause ? ` cause=${String(cause)}` : ""}`,
99
+ );
79
100
  }
80
101
  throw err;
81
102
  }
@@ -0,0 +1,7 @@
1
+ export const LIMITS = {
2
+ STREAM_TTL_MS: 10 * 60 * 1000,
3
+ ACTIVE_REPLY_TTL_MS: 60 * 60 * 1000,
4
+ DEFAULT_DEBOUNCE_MS: 500,
5
+ STREAM_MAX_BYTES: 20_480,
6
+ REQUEST_TIMEOUT_MS: 15_000,
7
+ };