@yanhaidao/wecom 2.3.160 → 2.3.180

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 (39) hide show
  1. package/README.md +235 -399
  2. package/SKILLS_CAL.md +895 -0
  3. package/SKILLS_DOC.md +2136 -0
  4. package/changelog/v2.3.18.md +22 -0
  5. package/index.ts +39 -3
  6. package/package.json +2 -3
  7. package/src/agent/handler.event-filter.test.ts +11 -0
  8. package/src/agent/handler.ts +732 -643
  9. package/src/app/account-runtime.ts +46 -20
  10. package/src/app/index.ts +19 -1
  11. package/src/capability/calendar/SKILLS_CHECKLIST.md +251 -0
  12. package/src/capability/calendar/client.ts +815 -0
  13. package/src/capability/calendar/index.ts +3 -0
  14. package/src/capability/calendar/schema.ts +417 -0
  15. package/src/capability/calendar/tool.ts +417 -0
  16. package/src/capability/calendar/types.ts +309 -0
  17. package/src/capability/doc/client.ts +567 -62
  18. package/src/capability/doc/schema.ts +419 -318
  19. package/src/capability/doc/tool.ts +1510 -1178
  20. package/src/capability/doc/types.ts +130 -14
  21. package/src/capability/mcp/index.ts +10 -0
  22. package/src/capability/mcp/schema.ts +107 -0
  23. package/src/capability/mcp/tool.ts +170 -0
  24. package/src/capability/mcp/transport.ts +394 -0
  25. package/src/channel.ts +70 -28
  26. package/src/config/schema.ts +71 -102
  27. package/src/outbound.test.ts +91 -14
  28. package/src/outbound.ts +143 -30
  29. package/src/runtime/reply-orchestrator.test.ts +35 -2
  30. package/src/runtime/reply-orchestrator.ts +14 -2
  31. package/src/runtime/session-manager.ts +20 -6
  32. package/src/runtime/source-registry.ts +165 -0
  33. package/src/transport/bot-ws/media.ts +269 -0
  34. package/src/transport/bot-ws/reply.test.ts +85 -17
  35. package/src/transport/bot-ws/reply.ts +109 -21
  36. package/src/transport/bot-ws/sdk-adapter.test.ts +64 -1
  37. package/src/transport/bot-ws/sdk-adapter.ts +88 -12
  38. package/.claude/settings.local.json +0 -11
  39. package/docs/update-content-fix.md +0 -135
@@ -0,0 +1,394 @@
1
+ import { generateReqId } from "@wecom/aibot-node-sdk";
2
+ import { getBotWsPushHandle } from "../../runtime.js";
3
+
4
+ const HTTP_REQUEST_TIMEOUT_MS = 30_000;
5
+ const MCP_CONFIG_FETCH_TIMEOUT_MS = 15_000;
6
+ const MCP_GET_CONFIG_CMD = "aibot_get_mcp_config";
7
+ const MCP_PLUGIN_VERSION = "wecom-dual-plane";
8
+ const LOG_TAG = "[wecom-mcp]";
9
+
10
+ interface JsonRpcRequest {
11
+ jsonrpc: "2.0";
12
+ id?: string;
13
+ method: string;
14
+ params?: Record<string, unknown>;
15
+ }
16
+
17
+ interface JsonRpcResponse {
18
+ jsonrpc: "2.0";
19
+ id: number | string;
20
+ result?: unknown;
21
+ error?: {
22
+ code: number;
23
+ message: string;
24
+ data?: unknown;
25
+ };
26
+ }
27
+
28
+ interface McpSession {
29
+ sessionId: string | null;
30
+ initialized: boolean;
31
+ stateless: boolean;
32
+ }
33
+
34
+ const CACHE_CLEAR_ERROR_CODES = new Set([-32001, -32002, -32003]);
35
+
36
+ const mcpConfigCache = new Map<string, Record<string, unknown>>();
37
+ const mcpSessionCache = new Map<string, McpSession>();
38
+ const statelessKeys = new Set<string>();
39
+ const inflightInitRequests = new Map<string, Promise<McpSession>>();
40
+
41
+ function cacheKey(accountId: string, category: string): string {
42
+ return `${accountId}::${category}`;
43
+ }
44
+
45
+ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
46
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
47
+ const timeoutPromise = new Promise<never>((_, reject) => {
48
+ timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
49
+ });
50
+ return Promise.race([promise, timeoutPromise]).finally(() => {
51
+ if (timeoutId) clearTimeout(timeoutId);
52
+ });
53
+ }
54
+
55
+ export class McpRpcError extends Error {
56
+ constructor(
57
+ public readonly code: number,
58
+ message: string,
59
+ public readonly data?: unknown,
60
+ ) {
61
+ super(message);
62
+ this.name = "McpRpcError";
63
+ }
64
+ }
65
+
66
+ export class McpHttpError extends Error {
67
+ constructor(
68
+ public readonly statusCode: number,
69
+ message: string,
70
+ ) {
71
+ super(message);
72
+ this.name = "McpHttpError";
73
+ }
74
+ }
75
+
76
+ async function fetchMcpConfig(
77
+ accountId: string,
78
+ category: string,
79
+ ): Promise<Record<string, unknown>> {
80
+ const handle = getBotWsPushHandle(accountId);
81
+ if (!handle?.isConnected()) {
82
+ throw new Error(`当前企微账号 MCP 服务未就绪:account=${accountId} 的 Bot WS 未连接。`);
83
+ }
84
+
85
+ const response = await withTimeout(
86
+ handle.replyCommand({
87
+ cmd: MCP_GET_CONFIG_CMD,
88
+ body: {
89
+ biz_type: category,
90
+ plugin_version: MCP_PLUGIN_VERSION,
91
+ },
92
+ headers: {
93
+ req_id: generateReqId("mcp_config"),
94
+ },
95
+ }),
96
+ MCP_CONFIG_FETCH_TIMEOUT_MS,
97
+ `MCP config fetch timed out after ${MCP_CONFIG_FETCH_TIMEOUT_MS}ms`,
98
+ );
99
+
100
+ const errcode = Number((response as { errcode?: number }).errcode ?? 0);
101
+ if (errcode !== 0) {
102
+ throw new Error(
103
+ `MCP 配置请求失败: errcode=${String((response as { errcode?: number }).errcode)} errmsg=${String((response as { errmsg?: string }).errmsg ?? "unknown")}`,
104
+ );
105
+ }
106
+
107
+ const body = (response as { body?: { url?: string } }).body;
108
+ if (!body?.url) {
109
+ throw new Error(`MCP 配置响应缺少 url 字段 (account=${accountId}, category=${category})`);
110
+ }
111
+
112
+ console.log(`${LOG_TAG} config ready account=${accountId} category=${category} url=${body.url}`);
113
+ return body as Record<string, unknown>;
114
+ }
115
+
116
+ async function getMcpUrl(accountId: string, category: string): Promise<string> {
117
+ const key = cacheKey(accountId, category);
118
+ const cached = mcpConfigCache.get(key);
119
+ if (cached?.url) {
120
+ return String(cached.url);
121
+ }
122
+ const body = await fetchMcpConfig(accountId, category);
123
+ mcpConfigCache.set(key, body);
124
+ return String(body.url);
125
+ }
126
+
127
+ async function sendRawJsonRpc(
128
+ url: string,
129
+ session: McpSession,
130
+ body: JsonRpcRequest,
131
+ ): Promise<{ rpcResult: unknown; newSessionId: string | null }> {
132
+ const controller = new AbortController();
133
+ const timeoutId = setTimeout(() => controller.abort(), HTTP_REQUEST_TIMEOUT_MS);
134
+ try {
135
+ const headers: Record<string, string> = {
136
+ "Content-Type": "application/json",
137
+ Accept: "application/json, text/event-stream",
138
+ };
139
+ if (session.sessionId) {
140
+ headers["Mcp-Session-Id"] = session.sessionId;
141
+ }
142
+
143
+ const response = await fetch(url, {
144
+ method: "POST",
145
+ headers,
146
+ body: JSON.stringify(body),
147
+ signal: controller.signal,
148
+ });
149
+ const newSessionId = response.headers.get("mcp-session-id");
150
+
151
+ if (!response.ok) {
152
+ throw new McpHttpError(
153
+ response.status,
154
+ `MCP HTTP 请求失败: ${response.status} ${response.statusText}`,
155
+ );
156
+ }
157
+
158
+ const contentLength = response.headers.get("content-length");
159
+ if (response.status === 204 || contentLength === "0") {
160
+ return { rpcResult: undefined, newSessionId };
161
+ }
162
+
163
+ const contentType = response.headers.get("content-type") ?? "";
164
+ if (contentType.includes("text/event-stream")) {
165
+ return {
166
+ rpcResult: await parseSseResponse(response),
167
+ newSessionId,
168
+ };
169
+ }
170
+
171
+ const text = await response.text();
172
+ if (!text.trim()) {
173
+ return { rpcResult: undefined, newSessionId };
174
+ }
175
+
176
+ const rpc = JSON.parse(text) as JsonRpcResponse;
177
+ if (rpc.error) {
178
+ throw new McpRpcError(
179
+ rpc.error.code,
180
+ `MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,
181
+ rpc.error.data,
182
+ );
183
+ }
184
+ return { rpcResult: rpc.result, newSessionId };
185
+ } catch (error) {
186
+ if (error instanceof DOMException && error.name === "AbortError") {
187
+ throw new Error(`MCP 请求超时 (${HTTP_REQUEST_TIMEOUT_MS}ms)`);
188
+ }
189
+ throw error;
190
+ } finally {
191
+ clearTimeout(timeoutId);
192
+ }
193
+ }
194
+
195
+ async function initializeSession(
196
+ accountId: string,
197
+ category: string,
198
+ url: string,
199
+ ): Promise<McpSession> {
200
+ const key = cacheKey(accountId, category);
201
+ const session: McpSession = { sessionId: null, initialized: false, stateless: false };
202
+
203
+ const initializeRequest: JsonRpcRequest = {
204
+ jsonrpc: "2.0",
205
+ id: generateReqId("mcp_init"),
206
+ method: "initialize",
207
+ params: {
208
+ protocolVersion: "2025-03-26",
209
+ capabilities: {},
210
+ clientInfo: { name: "wecom_mcp", version: MCP_PLUGIN_VERSION },
211
+ },
212
+ };
213
+
214
+ const initResult = await sendRawJsonRpc(url, session, initializeRequest);
215
+ if (initResult.newSessionId) {
216
+ session.sessionId = initResult.newSessionId;
217
+ }
218
+ if (!session.sessionId) {
219
+ session.stateless = true;
220
+ session.initialized = true;
221
+ statelessKeys.add(key);
222
+ mcpSessionCache.set(key, session);
223
+ return session;
224
+ }
225
+
226
+ const notifyRequest: JsonRpcRequest = {
227
+ jsonrpc: "2.0",
228
+ method: "notifications/initialized",
229
+ };
230
+ const notifyResult = await sendRawJsonRpc(url, session, notifyRequest);
231
+ if (notifyResult.newSessionId) {
232
+ session.sessionId = notifyResult.newSessionId;
233
+ }
234
+ session.initialized = true;
235
+ mcpSessionCache.set(key, session);
236
+ return session;
237
+ }
238
+
239
+ async function getOrCreateSession(
240
+ accountId: string,
241
+ category: string,
242
+ url: string,
243
+ ): Promise<McpSession> {
244
+ const key = cacheKey(accountId, category);
245
+ if (statelessKeys.has(key)) {
246
+ const cached = mcpSessionCache.get(key);
247
+ if (cached) return cached;
248
+ }
249
+
250
+ const cached = mcpSessionCache.get(key);
251
+ if (cached?.initialized) {
252
+ return cached;
253
+ }
254
+
255
+ const inflight = inflightInitRequests.get(key);
256
+ if (inflight) {
257
+ return inflight;
258
+ }
259
+
260
+ const promise = initializeSession(accountId, category, url).finally(() => {
261
+ inflightInitRequests.delete(key);
262
+ });
263
+ inflightInitRequests.set(key, promise);
264
+ return promise;
265
+ }
266
+
267
+ async function rebuildSession(
268
+ accountId: string,
269
+ category: string,
270
+ url: string,
271
+ ): Promise<McpSession> {
272
+ const key = cacheKey(accountId, category);
273
+ const inflight = inflightInitRequests.get(key);
274
+ if (inflight) return inflight;
275
+ const promise = initializeSession(accountId, category, url).finally(() => {
276
+ inflightInitRequests.delete(key);
277
+ });
278
+ inflightInitRequests.set(key, promise);
279
+ return promise;
280
+ }
281
+
282
+ async function parseSseResponse(response: Response): Promise<unknown> {
283
+ const text = await response.text();
284
+ const lines = text.split("\n");
285
+ let currentParts: string[] = [];
286
+ let lastEventData = "";
287
+
288
+ for (const line of lines) {
289
+ if (line.startsWith("data: ")) {
290
+ currentParts.push(line.slice(6));
291
+ continue;
292
+ }
293
+ if (line.startsWith("data:")) {
294
+ currentParts.push(line.slice(5));
295
+ continue;
296
+ }
297
+ if (line.trim() === "" && currentParts.length > 0) {
298
+ lastEventData = currentParts.join("\n").trim();
299
+ currentParts = [];
300
+ }
301
+ }
302
+ if (currentParts.length > 0) {
303
+ lastEventData = currentParts.join("\n").trim();
304
+ }
305
+ if (!lastEventData) {
306
+ throw new Error("SSE 响应中未包含有效数据");
307
+ }
308
+
309
+ const rpc = JSON.parse(lastEventData) as JsonRpcResponse;
310
+ if (rpc.error) {
311
+ throw new McpRpcError(
312
+ rpc.error.code,
313
+ `MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,
314
+ rpc.error.data,
315
+ );
316
+ }
317
+ return rpc.result;
318
+ }
319
+
320
+ export function clearWecomMcpCategoryCache(accountId: string, category: string): void {
321
+ const key = cacheKey(accountId, category);
322
+ console.log(`${LOG_TAG} clear cache account=${accountId} category=${category}`);
323
+ mcpConfigCache.delete(key);
324
+ mcpSessionCache.delete(key);
325
+ statelessKeys.delete(key);
326
+ inflightInitRequests.delete(key);
327
+ }
328
+
329
+ export function clearWecomMcpAccountCache(accountId: string): void {
330
+ const prefix = `${accountId}::`;
331
+ for (const key of [...mcpConfigCache.keys()]) {
332
+ if (key.startsWith(prefix)) mcpConfigCache.delete(key);
333
+ }
334
+ for (const key of [...mcpSessionCache.keys()]) {
335
+ if (key.startsWith(prefix)) mcpSessionCache.delete(key);
336
+ }
337
+ for (const key of [...statelessKeys]) {
338
+ if (key.startsWith(prefix)) statelessKeys.delete(key);
339
+ }
340
+ for (const key of [...inflightInitRequests.keys()]) {
341
+ if (key.startsWith(prefix)) inflightInitRequests.delete(key);
342
+ }
343
+ }
344
+
345
+ export interface McpToolInfo {
346
+ name: string;
347
+ description?: string;
348
+ inputSchema?: Record<string, unknown>;
349
+ }
350
+
351
+ export async function sendJsonRpc(
352
+ accountId: string,
353
+ category: string,
354
+ method: string,
355
+ params?: Record<string, unknown>,
356
+ ): Promise<unknown> {
357
+ const url = await getMcpUrl(accountId, category);
358
+ const body: JsonRpcRequest = {
359
+ jsonrpc: "2.0",
360
+ id: generateReqId("mcp_rpc"),
361
+ method,
362
+ ...(params !== undefined ? { params } : {}),
363
+ };
364
+
365
+ let session = await getOrCreateSession(accountId, category, url);
366
+
367
+ try {
368
+ const result = await sendRawJsonRpc(url, session, body);
369
+ if (result.newSessionId) {
370
+ session.sessionId = result.newSessionId;
371
+ }
372
+ return result.rpcResult;
373
+ } catch (error) {
374
+ if (error instanceof McpRpcError && CACHE_CLEAR_ERROR_CODES.has(error.code)) {
375
+ clearWecomMcpCategoryCache(accountId, category);
376
+ }
377
+ if (session.stateless) {
378
+ throw error;
379
+ }
380
+ if (error instanceof McpHttpError && error.statusCode === 404) {
381
+ mcpSessionCache.delete(cacheKey(accountId, category));
382
+ session = await rebuildSession(accountId, category, url);
383
+ const result = await sendRawJsonRpc(url, session, body);
384
+ if (result.newSessionId) {
385
+ session.sessionId = result.newSessionId;
386
+ }
387
+ return result.rpcResult;
388
+ }
389
+ console.error(
390
+ `${LOG_TAG} rpc failed account=${accountId} category=${category} method=${method} error=${error instanceof Error ? error.message : String(error)}`,
391
+ );
392
+ throw error;
393
+ }
394
+ }
package/src/channel.ts CHANGED
@@ -27,16 +27,22 @@ const meta = {
27
27
  selectionLabel: "WeCom (企业微信)",
28
28
  docsPath: "/channels/wecom",
29
29
  docsLabel: "企业微信",
30
- blurb: "企业微信官方推荐三方插件,默认 Bot WS 配置简单,支持主动发消息与 Agent 全能力。",
30
+ blurb:
31
+ "企业微信官方推荐三方插件,默认 Bot WS 配置简单,支持主动发消息与 Agent 全能力。",
31
32
  selectionDocsPrefix: "文档:",
32
33
  aliases: ["wechatwork", "wework", "qywx", "企微", "企业微信"],
33
34
  order: 85,
34
35
  quickstartAllowFrom: true,
35
36
  };
36
37
 
37
- function resolveAccountInboundPath(account: ResolvedWecomAccount): string | undefined {
38
+ function resolveAccountInboundPath(
39
+ account: ResolvedWecomAccount,
40
+ ): string | undefined {
38
41
  const derivedPaths = resolveDerivedPathSummary(account.accountId);
39
- if (account.bot?.primaryTransport === "webhook" && account.bot.webhookConfigured) {
42
+ if (
43
+ account.bot?.primaryTransport === "webhook" &&
44
+ account.bot.webhookConfigured
45
+ ) {
40
46
  return derivedPaths.botWebhook[0];
41
47
  }
42
48
  if (account.agent?.callbackConfigured) {
@@ -51,7 +57,9 @@ function normalizeWecomMessagingTarget(raw: string): string | undefined {
51
57
  if (/^wecom-agent:/i.test(trimmed)) {
52
58
  return trimmed;
53
59
  }
54
- return trimmed.replace(/^(wecom|wechatwork|wework|qywx):/i, "").trim() || undefined;
60
+ return (
61
+ trimmed.replace(/^(wecom|wechatwork|wework|qywx):/i, "").trim() || undefined
62
+ );
55
63
  }
56
64
 
57
65
  export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
@@ -68,9 +76,6 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
68
76
  blockStreaming: false,
69
77
  },
70
78
  reload: { configPrefixes: ["channels.wecom"] },
71
- // NOTE: We intentionally avoid Zod -> JSON Schema conversion at plugin-load time.
72
- // Some OpenClaw runtime environments load plugin modules via jiti in a way that can
73
- // surface zod `toJSONSchema()` binding issues (e.g. `this` undefined leading to `_zod` errors).
74
79
  // A permissive schema keeps config UX working while preventing startup failures.
75
80
  configSchema: {
76
81
  schema: {
@@ -81,8 +86,10 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
81
86
  },
82
87
  config: {
83
88
  listAccountIds: (cfg) => listWecomAccountIds(cfg as OpenClawConfig),
84
- resolveAccount: (cfg, accountId) => resolveWecomAccount({ cfg: cfg as OpenClawConfig, accountId }),
85
- defaultAccountId: (cfg) => resolveDefaultWecomAccountId(cfg as OpenClawConfig),
89
+ resolveAccount: (cfg, accountId) =>
90
+ resolveWecomAccount({ cfg: cfg as OpenClawConfig, accountId }),
91
+ defaultAccountId: (cfg) =>
92
+ resolveDefaultWecomAccountId(cfg as OpenClawConfig),
86
93
  setAccountEnabled: ({ cfg, accountId, enabled }) =>
87
94
  setAccountEnabledInConfigSection({
88
95
  cfg: cfg as OpenClawConfig,
@@ -126,9 +133,15 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
126
133
  };
127
134
  },
128
135
  resolveAllowFrom: ({ cfg, accountId }) => {
129
- const account = resolveWecomAccount({ cfg: cfg as OpenClawConfig, accountId });
136
+ const account = resolveWecomAccount({
137
+ cfg: cfg as OpenClawConfig,
138
+ accountId,
139
+ });
130
140
  // 与其他渠道保持一致:直接返回 allowFrom,空则允许所有人
131
- const allowFrom = account.agent?.config.dm?.allowFrom ?? account.bot?.config.dm?.allowFrom ?? [];
141
+ const allowFrom =
142
+ account.agent?.config.dm?.allowFrom ??
143
+ account.bot?.config.dm?.allowFrom ??
144
+ [];
132
145
  return allowFrom.map((entry) => String(entry));
133
146
  },
134
147
  formatAllowFrom: ({ allowFrom }) =>
@@ -170,20 +183,31 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
170
183
  transport: (snapshot as { transport?: string }).transport ?? null,
171
184
  ownerId: (snapshot as { ownerId?: string }).ownerId ?? null,
172
185
  health: (snapshot as { health?: string }).health ?? "idle",
173
- ownerDriftAt: (snapshot as { ownerDriftAt?: number | null }).ownerDriftAt ?? null,
186
+ ownerDriftAt:
187
+ (snapshot as { ownerDriftAt?: number | null }).ownerDriftAt ?? null,
174
188
  connected: (snapshot as { connected?: boolean }).connected,
175
189
  authenticated: (snapshot as { authenticated?: boolean }).authenticated,
176
190
  lastStartAt: snapshot.lastStartAt ?? null,
177
191
  lastStopAt: snapshot.lastStopAt ?? null,
178
192
  lastError: snapshot.lastError ?? null,
179
- lastErrorAt: (snapshot as { lastErrorAt?: number | null }).lastErrorAt ?? null,
193
+ lastErrorAt:
194
+ (snapshot as { lastErrorAt?: number | null }).lastErrorAt ?? null,
180
195
  lastInboundAt: snapshot.lastInboundAt ?? null,
181
196
  lastOutboundAt: snapshot.lastOutboundAt ?? null,
182
- recentInboundSummary: (snapshot as { recentInboundSummary?: string | null }).recentInboundSummary ?? null,
183
- recentOutboundSummary: (snapshot as { recentOutboundSummary?: string | null }).recentOutboundSummary ?? null,
184
- recentIssueCategory: (snapshot as { recentIssueCategory?: string | null }).recentIssueCategory ?? null,
185
- recentIssueSummary: (snapshot as { recentIssueSummary?: string | null }).recentIssueSummary ?? null,
186
- transportSessions: (snapshot as { transportSessions?: string[] }).transportSessions ?? [],
197
+ recentInboundSummary:
198
+ (snapshot as { recentInboundSummary?: string | null })
199
+ .recentInboundSummary ?? null,
200
+ recentOutboundSummary:
201
+ (snapshot as { recentOutboundSummary?: string | null })
202
+ .recentOutboundSummary ?? null,
203
+ recentIssueCategory:
204
+ (snapshot as { recentIssueCategory?: string | null })
205
+ .recentIssueCategory ?? null,
206
+ recentIssueSummary:
207
+ (snapshot as { recentIssueSummary?: string | null })
208
+ .recentIssueSummary ?? null,
209
+ transportSessions:
210
+ (snapshot as { transportSessions?: string[] }).transportSessions ?? [],
187
211
  probe: snapshot.probe,
188
212
  lastProbeAt: snapshot.lastProbeAt ?? null,
189
213
  }),
@@ -199,25 +223,43 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
199
223
  enabled: account.enabled,
200
224
  configured: account.configured && !conflict,
201
225
  webhookPath: resolveAccountInboundPath(account),
202
- primaryTransport: account.bot?.primaryTransport ?? (account.agent ? "agent-callback" : null),
203
- transport: (runtime as { transport?: string } | undefined)?.transport ?? null,
226
+ primaryTransport:
227
+ account.bot?.primaryTransport ??
228
+ (account.agent ? "agent-callback" : null),
229
+ transport:
230
+ (runtime as { transport?: string } | undefined)?.transport ?? null,
204
231
  ownerId: (runtime as { ownerId?: string } | undefined)?.ownerId ?? null,
205
232
  health: (runtime as { health?: string } | undefined)?.health ?? "idle",
206
- ownerDriftAt: (runtime as { ownerDriftAt?: number | null } | undefined)?.ownerDriftAt ?? null,
233
+ ownerDriftAt:
234
+ (runtime as { ownerDriftAt?: number | null } | undefined)
235
+ ?.ownerDriftAt ?? null,
207
236
  connected: (runtime as { connected?: boolean } | undefined)?.connected,
208
- authenticated: (runtime as { authenticated?: boolean } | undefined)?.authenticated,
237
+ authenticated: (runtime as { authenticated?: boolean } | undefined)
238
+ ?.authenticated,
209
239
  running: runtime?.running ?? false,
210
240
  lastStartAt: runtime?.lastStartAt ?? null,
211
241
  lastStopAt: runtime?.lastStopAt ?? null,
212
242
  lastError: runtime?.lastError ?? conflict?.message ?? null,
213
- lastErrorAt: (runtime as { lastErrorAt?: number | null } | undefined)?.lastErrorAt ?? null,
243
+ lastErrorAt:
244
+ (runtime as { lastErrorAt?: number | null } | undefined)
245
+ ?.lastErrorAt ?? null,
214
246
  lastInboundAt: runtime?.lastInboundAt ?? null,
215
247
  lastOutboundAt: runtime?.lastOutboundAt ?? null,
216
- recentInboundSummary: (runtime as { recentInboundSummary?: string | null } | undefined)?.recentInboundSummary ?? null,
217
- recentOutboundSummary: (runtime as { recentOutboundSummary?: string | null } | undefined)?.recentOutboundSummary ?? null,
218
- recentIssueCategory: (runtime as { recentIssueCategory?: string | null } | undefined)?.recentIssueCategory ?? null,
219
- recentIssueSummary: (runtime as { recentIssueSummary?: string | null } | undefined)?.recentIssueSummary ?? null,
220
- transportSessions: (runtime as { transportSessions?: string[] } | undefined)?.transportSessions ?? [],
248
+ recentInboundSummary:
249
+ (runtime as { recentInboundSummary?: string | null } | undefined)
250
+ ?.recentInboundSummary ?? null,
251
+ recentOutboundSummary:
252
+ (runtime as { recentOutboundSummary?: string | null } | undefined)
253
+ ?.recentOutboundSummary ?? null,
254
+ recentIssueCategory:
255
+ (runtime as { recentIssueCategory?: string | null } | undefined)
256
+ ?.recentIssueCategory ?? null,
257
+ recentIssueSummary:
258
+ (runtime as { recentIssueSummary?: string | null } | undefined)
259
+ ?.recentIssueSummary ?? null,
260
+ transportSessions:
261
+ (runtime as { transportSessions?: string[] } | undefined)
262
+ ?.transportSessions ?? [],
221
263
  dmPolicy: account.bot?.config.dm?.policy ?? "pairing",
222
264
  };
223
265
  },