shennian 0.2.75 → 0.2.76

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.
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process'
3
+ import fs from 'node:fs'
4
+ import os from 'node:os'
5
+ import path from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
9
+ const defaultHelper = path.join(
10
+ repoRoot,
11
+ 'packages/desktop/assets/native/wechat-rpa-win/win-x64/shennian-wechat-rpa-win.exe',
12
+ )
13
+
14
+ function takeOption(argv, name) {
15
+ const index = argv.indexOf(name)
16
+ if (index < 0) return null
17
+ const value = argv[index + 1]
18
+ if (!value || value.startsWith('--')) {
19
+ throw new Error(`Missing value for ${name}`)
20
+ }
21
+ argv.splice(index, 2)
22
+ return value
23
+ }
24
+
25
+ function printHelp() {
26
+ console.log(`Usage:
27
+ node scripts/wechat-rpa-win.mjs probe
28
+ node scripts/wechat-rpa-win.mjs probe --raise
29
+ node scripts/wechat-rpa-win.mjs open --group <name>
30
+ node scripts/wechat-rpa-win.mjs read-recent --group <name> --limit 5
31
+ node scripts/wechat-rpa-win.mjs send-text --group <name> --text <message>
32
+ node scripts/wechat-rpa-win.mjs send-files --group <name> --file <path> [--file <path> ...]
33
+ node scripts/wechat-rpa-win.mjs listen --group <name> --seconds 60 --poll-ms 1000
34
+ node scripts/wechat-rpa-win.mjs prepare-uia
35
+ node scripts/wechat-rpa-win.mjs capture --region window --output C:\\tmp\\wechat.png
36
+ node scripts/wechat-rpa-win.mjs click --x 480 --y 470 --no-raise
37
+ node scripts/wechat-rpa-win.mjs paste-text --text "hello"
38
+ node scripts/wechat-rpa-win.mjs paste-files --file C:\\tmp\\demo.png
39
+ node scripts/wechat-rpa-win.mjs press --keys "%s"
40
+
41
+ Options:
42
+ --helper <path> Override helper exe path.
43
+
44
+ Environment:
45
+ WECHAT_RPA_WIN_HELPER Override helper exe path.
46
+
47
+ Diagnostic note:
48
+ probe does not raise WeChat by default. Use probe --raise only for diagnostics.
49
+ restart-wechat-for-uia is intentionally hidden from normal help because it closes
50
+ WeChat. It is guarded in the native helper and must never be used by product flows.`)
51
+ }
52
+
53
+ async function main() {
54
+ const argv = process.argv.slice(2)
55
+ if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
56
+ printHelp()
57
+ return
58
+ }
59
+
60
+ if (process.platform !== 'win32' && !argv.includes('--allow-non-windows')) {
61
+ throw new Error(`wechat-rpa-win only runs on Windows. Current platform: ${os.platform()}`)
62
+ }
63
+
64
+ const allowIndex = argv.indexOf('--allow-non-windows')
65
+ if (allowIndex >= 0) argv.splice(allowIndex, 1)
66
+
67
+ const helper = takeOption(argv, '--helper') ?? process.env.WECHAT_RPA_WIN_HELPER ?? defaultHelper
68
+ if (!fs.existsSync(helper)) {
69
+ throw new Error(
70
+ `Windows helper not found: ${helper}\n` +
71
+ 'Build it on Windows with: pnpm --dir packages/desktop build:wechat-rpa-win',
72
+ )
73
+ }
74
+
75
+ const child = spawn(helper, argv, {
76
+ cwd: repoRoot,
77
+ windowsHide: true,
78
+ stdio: ['ignore', 'pipe', 'pipe'],
79
+ })
80
+
81
+ let stdout = ''
82
+ let stderr = ''
83
+ child.stdout.setEncoding('utf8')
84
+ child.stderr.setEncoding('utf8')
85
+ child.stdout.on('data', chunk => {
86
+ stdout += chunk
87
+ })
88
+ child.stderr.on('data', chunk => {
89
+ stderr += chunk
90
+ })
91
+
92
+ const exitCode = await new Promise((resolve, reject) => {
93
+ child.on('error', reject)
94
+ child.on('close', resolve)
95
+ })
96
+
97
+ if (stderr.trim()) {
98
+ process.stderr.write(stderr)
99
+ }
100
+
101
+ const trimmed = stdout.trim()
102
+ if (trimmed) {
103
+ try {
104
+ const json = JSON.parse(trimmed)
105
+ process.stdout.write(`${JSON.stringify(json, null, 2)}\n`)
106
+ } catch {
107
+ process.stdout.write(stdout)
108
+ }
109
+ }
110
+
111
+ if (exitCode !== 0) {
112
+ process.exit(exitCode ?? 1)
113
+ }
114
+ }
115
+
116
+ main().catch(error => {
117
+ console.error(error instanceof Error ? error.message : String(error))
118
+ process.exit(1)
119
+ })
@@ -63,6 +63,7 @@ function getFallbackCommandCandidates(command) {
63
63
  const dirs = [
64
64
  winPath.join(home, '.shennian', 'node'),
65
65
  winPath.join('C:\\', 'nvm4w', 'nodejs'),
66
+ winPath.join(localAppData, 'npm-global'),
66
67
  winPath.join(appData, 'npm'),
67
68
  winPath.join(localAppData, 'pnpm'),
68
69
  winPath.join(home, 'scoop', 'shims'),
@@ -85,12 +86,12 @@ function buildFallbackPathEnv(currentPath, command) {
85
86
  const parts = currentPath?.split(path.delimiter).filter(Boolean) ?? [];
86
87
  if (getProcessPlatform() === 'win32') {
87
88
  if (command && path.basename(command) !== command) {
88
- const commandDir = path.dirname(command);
89
+ const commandDir = path.win32.dirname(command);
89
90
  if (!parts.includes(commandDir))
90
91
  parts.unshift(commandDir);
91
92
  }
92
93
  for (const candidate of getFallbackCommandCandidates('shennian')) {
93
- const dir = path.dirname(candidate);
94
+ const dir = path.win32.dirname(candidate);
94
95
  if (!parts.includes(dir))
95
96
  parts.push(dir);
96
97
  }
@@ -144,6 +145,13 @@ function isWindowsCmdShim(filePath) {
144
145
  const ext = path.extname(filePath).toLowerCase();
145
146
  return ext === '.cmd' || ext === '.bat';
146
147
  }
148
+ function isWindowsNodeScript(filePath) {
149
+ const ext = path.extname(filePath).toLowerCase();
150
+ return ext === '.js' || ext === '.mjs' || ext === '.cjs';
151
+ }
152
+ function resolveWindowsNodeCommand() {
153
+ return lookupCommandPaths('node.exe')[0] ?? lookupCommandPaths('node')[0] ?? 'node';
154
+ }
147
155
  function quoteCmdArg(text) {
148
156
  return `"${text.replace(/"/g, '""')}"`;
149
157
  }
@@ -250,6 +258,13 @@ export function resolveBuiltinCommand(type) {
250
258
  }
251
259
  export function buildLaunchSpec(spec, runtimeArgs, cwd) {
252
260
  if (spec.kind === 'native') {
261
+ if (getProcessPlatform() === 'win32' && isWindowsNodeScript(spec.path)) {
262
+ return {
263
+ command: resolveWindowsNodeCommand(),
264
+ args: [spec.path, ...spec.args, ...runtimeArgs],
265
+ cwd,
266
+ };
267
+ }
253
268
  return {
254
269
  command: spec.path,
255
270
  args: [...spec.args, ...runtimeArgs],
@@ -352,6 +367,13 @@ export function buildCommandStringLaunchSpec(commandString, runtimeArgs, cwd) {
352
367
  cwd,
353
368
  };
354
369
  }
370
+ if (isWindowsNodeScript(invocation.path)) {
371
+ return {
372
+ command: resolveWindowsNodeCommand(),
373
+ args: [invocation.path, ...baseArgs, ...runtimeArgs],
374
+ cwd,
375
+ };
376
+ }
355
377
  return {
356
378
  command: invocation.path,
357
379
  args: [...baseArgs, ...runtimeArgs],
@@ -15,6 +15,7 @@ function buildReplyCommandInstructions(mode, sessionHint, workdirHint) {
15
15
  '发送图片:shennian manager external send-image --path "<图片绝对路径>" --caption "<可选说明>"',
16
16
  '发送视频:shennian manager external send-video --path "<视频绝对路径>" --caption "<可选说明>"',
17
17
  '发送文件:shennian manager external send-file --path "<文件绝对路径>" --caption "<可选说明>"',
18
+ '如果外部消息里显示“回复目标:rt_...”,多群监听或需要精确回复时在发送命令后追加 --reply-target "rt_..."。',
18
19
  '如果需要转发外部消息里的图片/视频/文件链接,先把链接下载到本地临时文件,再用对应的 --path 命令发送;不要直接发送短时效链接,也不要只口头说明已发送。',
19
20
  '只发送用户可见的最终内容,不要发送内部推理、工具日志或实现细节。',
20
21
  ].join('\n');
@@ -25,6 +26,7 @@ function buildReplyCommandInstructions(mode, sessionHint, workdirHint) {
25
26
  '发送图片:shennian external send-image --path "<图片绝对路径>" --caption "<可选说明>"',
26
27
  '发送视频:shennian external send-video --path "<视频绝对路径>" --caption "<可选说明>"',
27
28
  '发送文件:shennian external send-file --path "<文件绝对路径>" --caption "<可选说明>"',
29
+ '如果外部消息里显示“回复目标:rt_...”,多群监听或需要精确回复时在发送命令后追加 --reply-target "rt_..."。',
28
30
  '如果需要转发外部消息里的图片/视频/文件链接,先把链接下载到本地临时文件,再用对应的 --path 命令发送;不要直接发送短时效链接,也不要只口头说明已发送。',
29
31
  sessionHint,
30
32
  workdirHint,
@@ -124,4 +124,5 @@ export interface ExternalChannelAdapter {
124
124
  conversationName?: string;
125
125
  }>;
126
126
  runtimeStatus?(config: ExternalChannelConfig): Partial<ExternalChannelRuntimeStatus>;
127
+ syncNow?(config: ExternalChannelConfig): Promise<ExternalMessageEvent[] | void>;
127
128
  }
@@ -7,6 +7,7 @@ export declare class ChannelRuntime {
7
7
  private secrets;
8
8
  private adapters;
9
9
  private completedReplyKeys;
10
+ private recentMessages;
10
11
  constructor(onExternalMessage: (sessionId: string, event: ExternalMessageEvent) => void, createReplyTarget: (input: {
11
12
  managerSessionId: string;
12
13
  channelId: string;
@@ -17,7 +18,7 @@ export declare class ChannelRuntime {
17
18
  stop(): Promise<void>;
18
19
  ingest(event: ExternalMessageEvent & {
19
20
  managerSessionId?: string;
20
- }): void;
21
+ }): ExternalMessageEvent;
21
22
  reply(input: ExternalReply & {
22
23
  managerSessionId: string;
23
24
  }): Promise<{
@@ -53,6 +54,12 @@ export declare class ChannelRuntime {
53
54
  };
54
55
  }>;
55
56
  listManagerExternalChannels(managerSessionId: string): ExternalChannelSessionStatus[];
57
+ syncManagerWeChatRpaChannel(managerSessionId: string): Promise<{
58
+ channel: ExternalChannelView;
59
+ messages: ExternalMessageEvent[];
60
+ }>;
61
+ private recordRecentMessage;
62
+ private getRecentMessages;
56
63
  upsertManagerChannel(input: {
57
64
  id: string;
58
65
  managerSessionId: string;
@@ -84,7 +91,7 @@ export declare class ChannelRuntime {
84
91
  }>;
85
92
  canReply?: boolean;
86
93
  systemPrompt?: string;
87
- source?: 'macos-flow' | 'macos-probe' | 'fixture-jsonl';
94
+ source?: 'macos-flow' | 'macos-probe' | 'windows-visual-flow' | 'fixture-jsonl';
88
95
  pollIntervalMs?: number;
89
96
  recentLimit?: number;
90
97
  idleSeconds?: number;
@@ -18,14 +18,15 @@ export class ChannelRuntime {
18
18
  secrets = new ChannelSecretRegistry();
19
19
  adapters = new Map();
20
20
  completedReplyKeys = new Map();
21
+ recentMessages = new Map();
21
22
  constructor(onExternalMessage, createReplyTarget) {
22
23
  this.onExternalMessage = onExternalMessage;
23
24
  this.createReplyTarget = createReplyTarget;
24
- const wecom = new WeComChannelAdapter((event) => this.ingest({ type: 'external.message', ...event }));
25
+ const wecom = new WeComChannelAdapter((event) => this.ingest({ ...event, type: 'external.message' }));
25
26
  this.adapters.set(wecom.type, wecom);
26
- const websocket = new ExternalWebSocketChannelAdapter((event) => this.ingest({ type: 'external.message', ...event }));
27
+ const websocket = new ExternalWebSocketChannelAdapter((event) => this.ingest({ ...event, type: 'external.message' }));
27
28
  this.adapters.set(websocket.type, websocket);
28
- const wechatRpa = new WeChatRpaChannelAdapter((event) => this.ingest({ type: 'external.message', ...event }));
29
+ const wechatRpa = new WeChatRpaChannelAdapter((event) => this.ingest(event));
29
30
  this.adapters.set(wechatRpa.type, wechatRpa);
30
31
  }
31
32
  async start() {
@@ -49,7 +50,10 @@ export class ChannelRuntime {
49
50
  conversationId: event.conversationId,
50
51
  messageId: event.messageId,
51
52
  });
52
- this.onExternalMessage(sessionId, { ...event, replyTarget });
53
+ const normalized = { ...event, replyTarget };
54
+ this.onExternalMessage(sessionId, normalized);
55
+ this.recordRecentMessage(normalized);
56
+ return normalized;
53
57
  }
54
58
  async reply(input) {
55
59
  const config = this.configs.get(input.channelId);
@@ -242,6 +246,33 @@ export class ChannelRuntime {
242
246
  };
243
247
  });
244
248
  }
249
+ async syncManagerWeChatRpaChannel(managerSessionId) {
250
+ const config = this.configs.list()
251
+ .find((channel) => channel.enabled && channel.type === 'wechat-rpa' && (channel.sessionId ?? channel.managerSessionId) === managerSessionId);
252
+ if (!config)
253
+ throw new Error('No enabled WeChat RPA channel is bound to this session');
254
+ const adapter = this.adapters.get(config.type);
255
+ if (!adapter?.syncNow)
256
+ throw new Error('WeChat RPA channel does not support manual sync');
257
+ const recentSince = Date.now() - 2 * 60 * 1000;
258
+ const messages = mergeExternalMessages(await adapter.syncNow(config) ?? [], this.getRecentMessages(config.id, recentSince));
259
+ return {
260
+ channel: this.getManagerChannel(managerSessionId, 'wechat-rpa', { includeSecret: true }),
261
+ messages,
262
+ };
263
+ }
264
+ recordRecentMessage(event) {
265
+ const current = this.recentMessages.get(event.channelId) ?? [];
266
+ current.push(event);
267
+ this.recentMessages.set(event.channelId, current.slice(-50));
268
+ }
269
+ getRecentMessages(channelId, sinceMs) {
270
+ const messages = this.recentMessages.get(channelId) ?? [];
271
+ return messages.filter((event) => {
272
+ const receivedAt = Date.parse(event.receivedAt);
273
+ return !Number.isFinite(receivedAt) || receivedAt >= sinceMs;
274
+ });
275
+ }
245
276
  async upsertManagerChannel(input) {
246
277
  const previous = this.configs.get(input.id);
247
278
  const allConfigs = this.configs.list();
@@ -315,7 +346,7 @@ export class ChannelRuntime {
315
346
  secretRef: previous?.secretRef || `channel:${input.id}`,
316
347
  };
317
348
  const priorSecret = this.secrets.get(nextConfig.secretRef);
318
- const source = input.source || (priorSecret?.source === 'macos-probe' || priorSecret?.source === 'fixture-jsonl' || priorSecret?.source === 'macos-flow' ? priorSecret.source : 'macos-flow');
349
+ const source = input.source || (priorSecret?.source === 'macos-probe' || priorSecret?.source === 'fixture-jsonl' || priorSecret?.source === 'macos-flow' || priorSecret?.source === 'windows-visual-flow' ? priorSecret.source : defaultWeChatRpaSource());
319
350
  const configs = allConfigs
320
351
  .filter((channel) => channel.id !== nextConfig.id)
321
352
  .map((channel) => (channel.sessionId ?? channel.managerSessionId) === boundSessionId && channel.type === 'wechat-rpa'
@@ -416,15 +447,22 @@ function normalizeWeChatRpaGroups(groups) {
416
447
  function normalizeCloudOcrMode(value) {
417
448
  return value === 'fallback' || value === 'always' ? value : 'off';
418
449
  }
450
+ function defaultWeChatRpaSource() {
451
+ return process.platform === 'win32' ? 'windows-visual-flow' : 'macos-flow';
452
+ }
419
453
  function defaultWeChatRpaCloudOcrConfig() {
420
454
  const config = loadConfig();
421
455
  const serverUrl = (config.serverUrl || SERVERS.cn.url).replace(/\/+$/, '');
422
456
  const token = config.machineToken?.trim() || config.accessToken?.trim();
423
457
  return {
424
- cloudOcrUrl: `${serverUrl}/integrations/wechat-rpa/ocr`,
458
+ cloudOcrUrl: `${serverApiBaseUrl(serverUrl)}/integrations/wechat-rpa/ocr`,
425
459
  ...(token ? { cloudOcrToken: token } : {}),
426
460
  };
427
461
  }
462
+ function serverApiBaseUrl(serverUrl) {
463
+ const normalized = serverUrl.replace(/\/+$/, '');
464
+ return normalized.endsWith('/api') ? normalized : `${normalized}/api`;
465
+ }
428
466
  export function planExternalReplySends(channelType, input) {
429
467
  const parts = splitExternalReplyText(input.text);
430
468
  if (!parts.length && !input.attachment)
@@ -454,6 +492,18 @@ export function planExternalReplySends(channelType, input) {
454
492
  }
455
493
  return sends;
456
494
  }
495
+ function mergeExternalMessages(...groups) {
496
+ const seen = new Set();
497
+ const out = [];
498
+ for (const event of groups.flat()) {
499
+ const key = `${event.channelId}\n${event.conversationId}\n${event.messageId}`;
500
+ if (seen.has(key))
501
+ continue;
502
+ seen.add(key);
503
+ out.push(event);
504
+ }
505
+ return out;
506
+ }
457
507
  function replyCompletionKey(channelId, conversationId, idempotencyKey) {
458
508
  return crypto.createHash('sha256')
459
509
  .update(`${channelId}\n${conversationId}\n${idempotencyKey}`)
@@ -6,7 +6,7 @@ type ChannelSecretRecord = {
6
6
  token?: string;
7
7
  canReply?: boolean;
8
8
  systemPrompt?: string;
9
- source?: 'macos-probe' | 'macos-flow' | 'fixture-jsonl';
9
+ source?: 'macos-probe' | 'macos-flow' | 'windows-visual-flow' | 'fixture-jsonl';
10
10
  fixturePath?: string;
11
11
  pollIntervalMs?: number;
12
12
  groups?: Array<{
@@ -68,6 +68,11 @@ export type MacWeChatRpaCloudOcrUsage = {
68
68
  totalTokens?: number;
69
69
  };
70
70
  export declare function runMacWeChatRpaFlow(options: MacWeChatRpaFlowOptions): Promise<MacWeChatRpaFlowResult>;
71
+ export declare function buildCloudOcrImagePayload(screenshotPath: string, options: Pick<MacWeChatRpaFlowOptions, 'cloudOcrUrl' | 'cloudOcrToken'>): Promise<{
72
+ imageUrl: string;
73
+ imageObjectKey?: string;
74
+ } | null>;
75
+ export declare function cloudOcrImageUploadUrl(cloudOcrUrl?: string): string | null;
71
76
  export declare function selectCloudOcrRequest(result: Pick<MacWeChatRpaFlowResult, 'newMessages' | 'recentMessages' | 'screenshotPath' | 'postSendScreenshotPath' | 'sentReply' | 'sentAttachment'>, mode: 'off' | 'fallback' | 'always'): {
72
77
  screenshotPath: string;
73
78
  purpose: MacWeChatRpaCloudOcrPurpose;
@@ -49,6 +49,8 @@ export async function runMacWeChatRpaFlow(options) {
49
49
  const parsed = parseFlowStdout(stdout);
50
50
  if (parsed?.interrupted)
51
51
  return parsed;
52
+ if (parsed && hasPartialSendProgress(parsed))
53
+ return parsed;
52
54
  throw new Error(stderr.trim() || stdout.slice(0, 1_000) || execError.message || 'WeChat RPA flow failed');
53
55
  }
54
56
  try {
@@ -72,12 +74,17 @@ function parseFlowStdout(stdout) {
72
74
  return null;
73
75
  }
74
76
  }
77
+ function hasPartialSendProgress(result) {
78
+ return Boolean(result.sentReplyObserved || result.sentAttachmentObserved);
79
+ }
75
80
  async function maybeEnrichWithCloudOcr(result, options) {
76
81
  const mode = options.cloudOcrMode ?? 'off';
77
82
  const request = selectCloudOcrRequest(result, mode);
78
83
  if (!request || !options.cloudOcrUrl || !options.cloudOcrToken)
79
84
  return result;
80
- const imageBase64 = fs.readFileSync(request.screenshotPath).toString('base64');
85
+ const imagePayload = await buildCloudOcrImagePayload(request.screenshotPath, options);
86
+ if (!imagePayload)
87
+ return result;
81
88
  const response = await fetch(options.cloudOcrUrl, {
82
89
  method: 'POST',
83
90
  headers: {
@@ -85,7 +92,7 @@ async function maybeEnrichWithCloudOcr(result, options) {
85
92
  'content-type': 'application/json',
86
93
  },
87
94
  body: JSON.stringify({
88
- imageBase64,
95
+ ...imagePayload,
89
96
  mimeType: 'image/png',
90
97
  conversationName: options.groupName,
91
98
  purpose: request.purpose,
@@ -111,6 +118,47 @@ async function maybeEnrichWithCloudOcr(result, options) {
111
118
  recentMessages: mergeCloudMessages(result.recentMessages ?? [], cloudMessages).slice(-recentLimit),
112
119
  };
113
120
  }
121
+ export async function buildCloudOcrImagePayload(screenshotPath, options) {
122
+ const buffer = fs.readFileSync(screenshotPath);
123
+ const uploadUrl = cloudOcrImageUploadUrl(options.cloudOcrUrl);
124
+ if (uploadUrl && options.cloudOcrToken) {
125
+ try {
126
+ const response = await fetch(uploadUrl, {
127
+ method: 'POST',
128
+ headers: {
129
+ authorization: `Bearer ${options.cloudOcrToken}`,
130
+ 'content-type': 'image/png',
131
+ },
132
+ body: buffer,
133
+ });
134
+ if (response.ok) {
135
+ const payload = await response.json().catch(() => null);
136
+ if (payload?.imageUrl) {
137
+ return {
138
+ imageUrl: payload.imageUrl,
139
+ ...(payload.imageObjectKey ? { imageObjectKey: payload.imageObjectKey } : {}),
140
+ };
141
+ }
142
+ }
143
+ }
144
+ catch {
145
+ // Cloud OCR is an optional fallback; if image upload is unavailable, keep the local result.
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ export function cloudOcrImageUploadUrl(cloudOcrUrl) {
151
+ if (!cloudOcrUrl)
152
+ return null;
153
+ try {
154
+ const url = new URL(cloudOcrUrl);
155
+ url.pathname = url.pathname.replace(/\/ocr\/?$/, '/ocr/image');
156
+ return url.toString();
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
114
162
  export function selectCloudOcrRequest(result, mode) {
115
163
  if (!shouldUseCloudOcr(result, mode))
116
164
  return null;
@@ -0,0 +1,36 @@
1
+ import type { ExecFileOptions } from 'node:child_process';
2
+ import type { MacWeChatRpaAttachment, MacWeChatRpaFlowOptions, MacWeChatRpaFlowResult } from './macos-flow.js';
3
+ type WindowsVisualObservation = {
4
+ text?: string;
5
+ confidence?: number;
6
+ role?: string;
7
+ attachment?: MacWeChatRpaAttachment;
8
+ };
9
+ type WindowsVisualSent = {
10
+ type?: string;
11
+ text?: string;
12
+ files?: string[];
13
+ observations?: WindowsVisualObservation[];
14
+ };
15
+ type WindowsVisualSummary = {
16
+ ok?: boolean;
17
+ group?: string;
18
+ captureDir?: string;
19
+ recentMessages?: WindowsVisualObservation[];
20
+ sent?: WindowsVisualSent[];
21
+ artifacts?: string[];
22
+ error?: string;
23
+ };
24
+ export type WindowsWeChatRpaVisualFlowOptions = MacWeChatRpaFlowOptions & {
25
+ helperPath?: string;
26
+ ocrFixturePath?: string;
27
+ attachmentPaths?: string[];
28
+ };
29
+ export declare function buildWindowsVisualFlowExec(options: WindowsWeChatRpaVisualFlowOptions): {
30
+ command: string;
31
+ args: string[];
32
+ execOptions: ExecFileOptions;
33
+ };
34
+ export declare function runWindowsWeChatRpaVisualFlow(options: WindowsWeChatRpaVisualFlowOptions): Promise<MacWeChatRpaFlowResult>;
35
+ export declare function windowsSummaryToFlowResult(options: Pick<WindowsWeChatRpaVisualFlowOptions, 'groupName' | 'replyText' | 'attachmentPath' | 'attachmentPaths'>, summary: WindowsVisualSummary): MacWeChatRpaFlowResult;
36
+ export {};