shennian 0.2.86 → 0.2.88
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/dist/src/agents/adapter.d.ts +2 -1
- package/dist/src/agents/codex-utils.d.ts +5 -0
- package/dist/src/agents/codex-utils.js +5 -0
- package/dist/src/agents/codex.js +2 -2
- package/dist/src/agents/model-registry/discovery.js +2 -1
- package/dist/src/channels/base.d.ts +1 -13
- package/dist/src/channels/runtime.d.ts +0 -3
- package/dist/src/channels/runtime.js +1 -5
- package/dist/src/channels/secret-registry.d.ts +0 -4
- package/dist/src/channels/wechat-rpa/macos-flow.d.ts +0 -28
- package/dist/src/channels/wechat-rpa/macos-flow.js +1 -134
- package/dist/src/channels/wechat-rpa.js +27 -55
- package/dist/src/commands/daemon.js +1 -0
- package/dist/src/commands/manager.d.ts +1 -1
- package/dist/src/commands/manager.js +3 -10
- package/dist/src/index.js +2 -1
- package/dist/src/manager/runtime.js +0 -6
- package/dist/src/native-fusion/config.d.ts +1 -0
- package/dist/src/native-fusion/config.js +5 -0
- package/dist/src/session/handlers/chat.js +67 -4
- package/dist/src/session/manager.js +8 -0
- package/dist/src/session/types.d.ts +4 -1
- package/package.json +1 -1
- package/dist/scripts/wechat-rpa-win-visual.mjs +0 -1735
- package/dist/scripts/wechat-rpa-win.mjs +0 -352
- package/dist/src/channels/wechat-rpa/windows-visual-flow.d.ts +0 -40
- package/dist/src/channels/wechat-rpa/windows-visual-flow.js +0 -189
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { AgentType, ChatAttachmentMeta, ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
|
+
import type { AgentType, ChatAttachmentMeta, ExternalChannelSessionStatus, SessionRunPhase } from '@shennian/wire';
|
|
3
3
|
export type AgentEvent = {
|
|
4
4
|
state: string;
|
|
5
5
|
runId: string;
|
|
6
6
|
seq: number;
|
|
7
7
|
text?: string;
|
|
8
8
|
thinking?: boolean;
|
|
9
|
+
runPhase?: SessionRunPhase;
|
|
9
10
|
name?: string;
|
|
10
11
|
args?: Record<string, unknown>;
|
|
11
12
|
result?: string;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export declare function makeThreadTitle(text: string): string;
|
|
2
|
+
export declare const CODEX_APP_SERVER_CLIENT_INFO: {
|
|
3
|
+
readonly name: "codex-tui";
|
|
4
|
+
readonly title: "Codex TUI";
|
|
5
|
+
readonly version: "0.0.0";
|
|
6
|
+
};
|
|
2
7
|
export declare function isCodexCollabAgentToolName(name: string): boolean;
|
|
3
8
|
export declare function isCodexCollabAgentItem(item: Record<string, unknown>): boolean;
|
|
4
9
|
export declare function isCodexLegacyCollabAgentItem(item: {
|
|
@@ -5,6 +5,11 @@ export function makeThreadTitle(text) {
|
|
|
5
5
|
const title = firstLine.trim().slice(0, 80);
|
|
6
6
|
return title || 'Shennian';
|
|
7
7
|
}
|
|
8
|
+
export const CODEX_APP_SERVER_CLIENT_INFO = {
|
|
9
|
+
name: 'codex-tui',
|
|
10
|
+
title: 'Codex TUI',
|
|
11
|
+
version: '0.0.0',
|
|
12
|
+
};
|
|
8
13
|
const CODEX_COLLAB_AGENT_TOOL_NAMES = new Set([
|
|
9
14
|
'spawnAgent',
|
|
10
15
|
'sendInput',
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -6,7 +6,7 @@ import { AgentAdapter, registerAgent } from './adapter.js';
|
|
|
6
6
|
import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
|
|
7
7
|
import { buildAgentProcessEnv } from '../agent-env.js';
|
|
8
8
|
import { ensurePlatformInstructionsFile } from './platform-instructions.js';
|
|
9
|
-
import { extractAppServerErrorMessage, formatCodexErrorMessage, formatTransientCodexStatus, isCodexCollabAgentItem, isCodexCollabAgentToolName, isCodexLegacyCollabAgentItem, isCodexUnsupportedEffortError, isMissingCodexRolloutError, isTransientCodexErrorMessage, looksLikeCodexInteractiveAuthPrompt, looksLikeFatalCodexStderr, makeThreadTitle, normalizeCodexModelId, normalizeCodexReasoningEffort, normalizeCodexStderr, normalizeTerminalText, safeStringify, stripGitDirectiveArtifacts, } from './codex-utils.js';
|
|
9
|
+
import { CODEX_APP_SERVER_CLIENT_INFO, extractAppServerErrorMessage, formatCodexErrorMessage, formatTransientCodexStatus, isCodexCollabAgentItem, isCodexCollabAgentToolName, isCodexLegacyCollabAgentItem, isCodexUnsupportedEffortError, isMissingCodexRolloutError, isTransientCodexErrorMessage, looksLikeCodexInteractiveAuthPrompt, looksLikeFatalCodexStderr, makeThreadTitle, normalizeCodexModelId, normalizeCodexReasoningEffort, normalizeCodexStderr, normalizeTerminalText, safeStringify, stripGitDirectiveArtifacts, } from './codex-utils.js';
|
|
10
10
|
export { isCodexUnsupportedEffortError, isMissingCodexRolloutError, normalizeCodexModelId, normalizeCodexReasoningEffort, } from './codex-utils.js';
|
|
11
11
|
function buildCodexTextInput(text, attachments) {
|
|
12
12
|
const images = attachments
|
|
@@ -239,7 +239,7 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
239
239
|
async initializeAppServer(modelId) {
|
|
240
240
|
const codexModelId = normalizeCodexModelId(modelId);
|
|
241
241
|
await this.sendRpc('initialize', {
|
|
242
|
-
clientInfo:
|
|
242
|
+
clientInfo: CODEX_APP_SERVER_CLIENT_INFO,
|
|
243
243
|
capabilities: { experimentalApi: true },
|
|
244
244
|
});
|
|
245
245
|
if (this.agentSessionId) {
|
|
@@ -6,6 +6,7 @@ import { fallbackClaudeAliasModels, discoverClaudeAliasModelsFromEnv, fallbackGe
|
|
|
6
6
|
import { runResolvedCommand } from './runner.js';
|
|
7
7
|
import { DISCOVERY_WORKDIR } from './types.js';
|
|
8
8
|
import { buildAgentProcessEnv, readLatestUserEnv } from '../../agent-env.js';
|
|
9
|
+
import { CODEX_APP_SERVER_CLIENT_INFO } from '../codex-utils.js';
|
|
9
10
|
function sendAppServerRpc(proc, pending, id, method, params, timeoutMs) {
|
|
10
11
|
if (!proc.stdin)
|
|
11
12
|
return Promise.reject(new Error('codex app-server stdin unavailable'));
|
|
@@ -59,7 +60,7 @@ async function discoverCodexModelsViaAppServer(spec) {
|
|
|
59
60
|
});
|
|
60
61
|
try {
|
|
61
62
|
await sendAppServerRpc(proc, pending, seq++, 'initialize', {
|
|
62
|
-
clientInfo:
|
|
63
|
+
clientInfo: CODEX_APP_SERVER_CLIENT_INFO,
|
|
63
64
|
capabilities: { experimentalApi: true },
|
|
64
65
|
}, 10_000);
|
|
65
66
|
const [modelList, configRead] = await Promise.all([
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
|
-
export type ExternalChannelRuntimeStatus = Pick<ExternalChannelSessionStatus, 'wechatRpaRuntimeState' | 'wechatRpaLastRunAt' | 'wechatRpaLastMessageAt' | 'wechatRpaLastInterruptedAt' | 'wechatRpaPendingReplyCount' | 'wechatRpaLastError' | 'wechatRpaLastRunId' | 'wechatRpaLastTracePath' | 'wechatRpaLastTraceSummary' | 'wechatRpaRecentTaskSummaries'
|
|
2
|
+
export type ExternalChannelRuntimeStatus = Pick<ExternalChannelSessionStatus, 'wechatRpaRuntimeState' | 'wechatRpaLastRunAt' | 'wechatRpaLastMessageAt' | 'wechatRpaLastInterruptedAt' | 'wechatRpaPendingReplyCount' | 'wechatRpaLastError' | 'wechatRpaLastRunId' | 'wechatRpaLastTracePath' | 'wechatRpaLastTraceSummary' | 'wechatRpaRecentTaskSummaries'>;
|
|
3
3
|
export type ExternalChannelType = 'wecom' | 'websocket' | 'wechat-rpa';
|
|
4
4
|
export type ExternalChannelConfig = {
|
|
5
5
|
id: string;
|
|
@@ -42,9 +42,6 @@ export type ExternalChannelView = {
|
|
|
42
42
|
downloadAttachments?: boolean;
|
|
43
43
|
downloadAttachmentsDir?: string;
|
|
44
44
|
selfNickname?: string | null;
|
|
45
|
-
cloudOcrUrl?: string;
|
|
46
|
-
cloudOcrToken?: string;
|
|
47
|
-
cloudOcrMode?: string;
|
|
48
45
|
wechatRpaRuntimeState?: ExternalChannelSessionStatus['wechatRpaRuntimeState'];
|
|
49
46
|
wechatRpaLastRunAt?: string | null;
|
|
50
47
|
wechatRpaLastMessageAt?: string | null;
|
|
@@ -54,15 +51,6 @@ export type ExternalChannelView = {
|
|
|
54
51
|
wechatRpaLastTracePath?: string | null;
|
|
55
52
|
wechatRpaLastTraceSummary?: string | null;
|
|
56
53
|
wechatRpaRecentTaskSummaries?: ExternalChannelSessionStatus['wechatRpaRecentTaskSummaries'];
|
|
57
|
-
wechatRpaLastCloudOcrAt?: string | null;
|
|
58
|
-
wechatRpaLastCloudOcrPurpose?: string | null;
|
|
59
|
-
wechatRpaLastCloudOcrRequestId?: string | null;
|
|
60
|
-
wechatRpaLastCloudOcrImageHash?: string | null;
|
|
61
|
-
wechatRpaLastCloudOcrUsage?: {
|
|
62
|
-
inputTokens?: number;
|
|
63
|
-
outputTokens?: number;
|
|
64
|
-
totalTokens?: number;
|
|
65
|
-
} | null;
|
|
66
54
|
};
|
|
67
55
|
export type ExternalMessageEvent = {
|
|
68
56
|
type: 'external.message';
|
|
@@ -101,9 +101,6 @@ export declare class ChannelRuntime {
|
|
|
101
101
|
downloadAttachmentsDir?: string;
|
|
102
102
|
selfNickname?: string;
|
|
103
103
|
flowScriptPath?: string;
|
|
104
|
-
cloudOcrUrl?: string;
|
|
105
|
-
cloudOcrToken?: string;
|
|
106
|
-
cloudOcrMode?: 'off' | 'fallback' | 'always';
|
|
107
104
|
}): Promise<ExternalChannelView>;
|
|
108
105
|
}
|
|
109
106
|
export type ExternalReplySendPlanItem = {
|
|
@@ -357,7 +357,6 @@ export class ChannelRuntime {
|
|
|
357
357
|
: channel);
|
|
358
358
|
configs.push(nextConfig);
|
|
359
359
|
this.configs.replaceAll(configs);
|
|
360
|
-
const cloudOcrMode = 'off';
|
|
361
360
|
this.secrets.upsert(nextConfig.secretRef, {
|
|
362
361
|
type: 'wechat-rpa',
|
|
363
362
|
source,
|
|
@@ -371,9 +370,6 @@ export class ChannelRuntime {
|
|
|
371
370
|
downloadAttachmentsDir: input.downloadAttachmentsDir?.trim() || stringOrUndefined(priorSecret?.downloadAttachmentsDir),
|
|
372
371
|
selfNickname: input.selfNickname?.trim() || stringOrUndefined(priorSecret?.selfNickname),
|
|
373
372
|
flowScriptPath: input.flowScriptPath?.trim() || stringOrUndefined(priorSecret?.flowScriptPath),
|
|
374
|
-
cloudOcrUrl: '',
|
|
375
|
-
cloudOcrToken: '',
|
|
376
|
-
cloudOcrMode,
|
|
377
373
|
canReply: input.canReply ?? priorSecret?.canReply ?? false,
|
|
378
374
|
systemPrompt: input.systemPrompt ?? (typeof priorSecret?.systemPrompt === 'string' ? priorSecret.systemPrompt : ''),
|
|
379
375
|
});
|
|
@@ -445,7 +441,7 @@ function normalizeWeChatRpaGroups(groups) {
|
|
|
445
441
|
return result;
|
|
446
442
|
}
|
|
447
443
|
function defaultWeChatRpaSource() {
|
|
448
|
-
return
|
|
444
|
+
return 'wechat-rpa-lab';
|
|
449
445
|
}
|
|
450
446
|
export function planExternalReplySends(channelType, input) {
|
|
451
447
|
const parts = splitExternalReplyText(input.text);
|
|
@@ -19,11 +19,7 @@ type ChannelSecretRecord = {
|
|
|
19
19
|
selfNickname?: string;
|
|
20
20
|
idleSeconds?: number;
|
|
21
21
|
recentLimit?: number;
|
|
22
|
-
readMode?: 'local-ocr' | 'hybrid-vlm';
|
|
23
22
|
flowScriptPath?: string;
|
|
24
|
-
cloudOcrUrl?: string;
|
|
25
|
-
cloudOcrToken?: string;
|
|
26
|
-
cloudOcrMode?: 'off' | 'fallback' | 'always';
|
|
27
23
|
updatedAt: string;
|
|
28
24
|
};
|
|
29
25
|
export declare class ChannelSecretRegistry {
|
|
@@ -10,10 +10,6 @@ export type MacWeChatRpaFlowOptions = {
|
|
|
10
10
|
recentLimit?: number;
|
|
11
11
|
downloadAttachmentsDir?: string;
|
|
12
12
|
timeoutMs?: number;
|
|
13
|
-
cloudOcrUrl?: string;
|
|
14
|
-
cloudOcrToken?: string;
|
|
15
|
-
cloudOcrMode?: 'off' | 'fallback' | 'always';
|
|
16
|
-
cloudOcrChannelId?: string;
|
|
17
13
|
};
|
|
18
14
|
export type MacWeChatRpaFlowResult = {
|
|
19
15
|
ok: boolean;
|
|
@@ -30,14 +26,8 @@ export type MacWeChatRpaFlowResult = {
|
|
|
30
26
|
sentAttachment?: boolean;
|
|
31
27
|
sentAttachmentObserved?: boolean;
|
|
32
28
|
postSendScreenshotPath?: string;
|
|
33
|
-
cloudOcrPurpose?: MacWeChatRpaCloudOcrPurpose;
|
|
34
|
-
cloudOcrObservations?: MacWeChatRpaCloudOcrObservation[];
|
|
35
|
-
cloudOcrRequestId?: string;
|
|
36
|
-
cloudOcrImageHash?: string;
|
|
37
|
-
cloudOcrUsage?: MacWeChatRpaCloudOcrUsage;
|
|
38
29
|
error?: string;
|
|
39
30
|
};
|
|
40
|
-
export type MacWeChatRpaCloudOcrPurpose = 'message-read' | 'attachment-localization' | 'send-confirmation';
|
|
41
31
|
export type MacWeChatRpaFlowMessage = {
|
|
42
32
|
id?: string;
|
|
43
33
|
text?: string;
|
|
@@ -62,22 +52,4 @@ export type MacWeChatRpaAttachment = {
|
|
|
62
52
|
expiresAt?: string;
|
|
63
53
|
providerError?: string;
|
|
64
54
|
};
|
|
65
|
-
export type MacWeChatRpaCloudOcrObservation = {
|
|
66
|
-
text: string;
|
|
67
|
-
confidence?: number;
|
|
68
|
-
role?: string;
|
|
69
|
-
attachment?: MacWeChatRpaAttachment;
|
|
70
|
-
};
|
|
71
|
-
export type MacWeChatRpaCloudOcrUsage = {
|
|
72
|
-
inputTokens?: number;
|
|
73
|
-
outputTokens?: number;
|
|
74
|
-
totalTokens?: number;
|
|
75
|
-
};
|
|
76
55
|
export declare function runMacWeChatRpaFlow(options: MacWeChatRpaFlowOptions): Promise<MacWeChatRpaFlowResult>;
|
|
77
|
-
export declare function selectCloudOcrRequest(result: Pick<MacWeChatRpaFlowResult, 'newMessages' | 'recentMessages' | 'screenshotPath' | 'postSendScreenshotPath' | 'sentReply' | 'sentAttachment'>, mode: 'off' | 'fallback' | 'always'): {
|
|
78
|
-
screenshotPath: string;
|
|
79
|
-
purpose: MacWeChatRpaCloudOcrPurpose;
|
|
80
|
-
} | null;
|
|
81
|
-
export declare function shouldUseCloudOcr(result: Pick<MacWeChatRpaFlowResult, 'newMessages' | 'recentMessages'>, mode: 'off' | 'fallback' | 'always'): boolean;
|
|
82
|
-
export declare function mergeCloudMessages(localMessages: MacWeChatRpaFlowMessage[], cloudMessages: MacWeChatRpaFlowMessage[]): MacWeChatRpaFlowMessage[];
|
|
83
|
-
export declare function messagesFromCloudOcrObservations(groupName: string, observations: MacWeChatRpaCloudOcrObservation[]): MacWeChatRpaFlowMessage[];
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// @test src/__tests__/wechat-rpa-normalizer.test.ts
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import crypto from 'node:crypto';
|
|
6
5
|
import { execFile } from 'node:child_process';
|
|
7
6
|
import { promisify } from 'node:util';
|
|
8
7
|
const execFileAsync = promisify(execFile);
|
|
@@ -61,7 +60,7 @@ export async function runMacWeChatRpaFlow(options) {
|
|
|
61
60
|
return parsed;
|
|
62
61
|
if (!parsed.ok)
|
|
63
62
|
throw new Error(parsed.error || 'WeChat RPA flow failed');
|
|
64
|
-
return
|
|
63
|
+
return parsed;
|
|
65
64
|
}
|
|
66
65
|
catch (error) {
|
|
67
66
|
const detail = stderr.trim() || stdout.slice(0, 1_000);
|
|
@@ -82,138 +81,6 @@ function hasPartialSendProgress(result) {
|
|
|
82
81
|
function hasSendAttempt(result) {
|
|
83
82
|
return Boolean(result.sentReply || result.sentAttachment || result.sentReplyObserved || result.sentAttachmentObserved);
|
|
84
83
|
}
|
|
85
|
-
async function maybeEnrichWithCloudOcr(result, options) {
|
|
86
|
-
void options;
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
export function selectCloudOcrRequest(result, mode) {
|
|
90
|
-
if (!shouldUseCloudOcr(result, mode))
|
|
91
|
-
return null;
|
|
92
|
-
if ((result.sentReply || result.sentAttachment) && result.postSendScreenshotPath) {
|
|
93
|
-
return { screenshotPath: result.postSendScreenshotPath, purpose: 'send-confirmation' };
|
|
94
|
-
}
|
|
95
|
-
if (!result.screenshotPath)
|
|
96
|
-
return null;
|
|
97
|
-
const messages = [...(result.newMessages ?? []), ...(result.recentMessages ?? [])];
|
|
98
|
-
const hasUnresolvedAttachment = messages.some((message) => (message.attachments ?? []).some((attachment) => attachment.availability === 'metadata-only'
|
|
99
|
-
|| attachment.availability === 'pending-download'
|
|
100
|
-
|| Boolean(attachment.providerError)));
|
|
101
|
-
return {
|
|
102
|
-
screenshotPath: result.screenshotPath,
|
|
103
|
-
purpose: hasUnresolvedAttachment ? 'attachment-localization' : 'message-read',
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
export function shouldUseCloudOcr(result, mode) {
|
|
107
|
-
if (mode === 'off')
|
|
108
|
-
return false;
|
|
109
|
-
if (mode === 'always')
|
|
110
|
-
return true;
|
|
111
|
-
const messages = [...(result.newMessages ?? []), ...(result.recentMessages ?? [])];
|
|
112
|
-
if (!(result.newMessages?.length))
|
|
113
|
-
return true;
|
|
114
|
-
if (messages.some((message) => Number.isFinite(message.confidence) && Number(message.confidence) < 0.65))
|
|
115
|
-
return true;
|
|
116
|
-
return messages.some((message) => (message.attachments ?? []).some((attachment) => attachment.availability === 'metadata-only'
|
|
117
|
-
|| attachment.availability === 'pending-download'
|
|
118
|
-
|| Boolean(attachment.providerError)));
|
|
119
|
-
}
|
|
120
|
-
export function mergeCloudMessages(localMessages, cloudMessages) {
|
|
121
|
-
const merged = [...localMessages];
|
|
122
|
-
for (const cloud of cloudMessages) {
|
|
123
|
-
const signature = messageSignature(cloud);
|
|
124
|
-
const index = merged.findIndex((message) => messageSignature(message) === signature);
|
|
125
|
-
if (index < 0) {
|
|
126
|
-
merged.push(cloud);
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
const existingAttachments = merged[index]?.attachments ?? [];
|
|
130
|
-
const cloudAttachments = cloud.attachments ?? [];
|
|
131
|
-
if (!existingAttachments.length && cloudAttachments.length) {
|
|
132
|
-
const existing = merged[index] ?? {};
|
|
133
|
-
merged[index] = { ...existing, attachments: cloudAttachments };
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return merged;
|
|
137
|
-
}
|
|
138
|
-
export function messagesFromCloudOcrObservations(groupName, observations) {
|
|
139
|
-
const messages = [];
|
|
140
|
-
for (const item of observations) {
|
|
141
|
-
const text = String(item.text || '').trim();
|
|
142
|
-
if (!text)
|
|
143
|
-
continue;
|
|
144
|
-
const role = String(item.role || 'unknown');
|
|
145
|
-
if (!['message', 'attachment', 'unknown'].includes(role))
|
|
146
|
-
continue;
|
|
147
|
-
const attachment = normalizeCloudAttachment(item.attachment) ?? (role === 'attachment' ? attachmentFromText(text) : null);
|
|
148
|
-
messages.push({
|
|
149
|
-
id: `cloud:${stableId(`${groupName}\n${role}\n${text}`)}`,
|
|
150
|
-
text,
|
|
151
|
-
confidence: item.confidence,
|
|
152
|
-
attachments: attachment ? [attachment] : [],
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
return messages;
|
|
156
|
-
}
|
|
157
|
-
function messageSignature(message) {
|
|
158
|
-
const text = String(message.text || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
159
|
-
const attachments = (message.attachments ?? [])
|
|
160
|
-
.map((attachment) => `${attachment.type}:${attachment.name || ''}:${attachment.mimeType || ''}`)
|
|
161
|
-
.sort()
|
|
162
|
-
.join('|');
|
|
163
|
-
return text || attachments || String(message.id || '');
|
|
164
|
-
}
|
|
165
|
-
function normalizeCloudAttachment(value) {
|
|
166
|
-
if (!value || typeof value !== 'object')
|
|
167
|
-
return null;
|
|
168
|
-
const record = value;
|
|
169
|
-
const type = String(record.type || 'file').trim() || 'file';
|
|
170
|
-
const name = String(record.name || '').replace(/\s+/g, ' ').trim();
|
|
171
|
-
const mimeType = String(record.mimeType || '').replace(/\s+/g, ' ').trim();
|
|
172
|
-
const size = Number(record.size);
|
|
173
|
-
return {
|
|
174
|
-
type: ['image', 'video', 'audio', 'file'].includes(type) ? type : 'file',
|
|
175
|
-
...(name ? { name } : {}),
|
|
176
|
-
...(mimeType ? { mimeType } : {}),
|
|
177
|
-
...(Number.isFinite(size) && size > 0 ? { size } : {}),
|
|
178
|
-
availability: 'metadata-only',
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
function attachmentFromText(text) {
|
|
182
|
-
const matches = Array.from(text.matchAll(/[\p{L}\p{N}_().[\]-]+\.(png|jpe?g|gif|webp|heic|mp4|mov|avi|mkv|pdf|docx?|xlsx?|pptx?|txt|md|csv|zip|rar)/giu));
|
|
183
|
-
const filename = matches.at(-1)?.[0]?.trim();
|
|
184
|
-
if (!filename)
|
|
185
|
-
return null;
|
|
186
|
-
const ext = path.extname(filename).toLowerCase();
|
|
187
|
-
return { type: attachmentTypeFromExt(ext), name: filename, mimeType: mimeTypeFromExt(ext), availability: 'metadata-only' };
|
|
188
|
-
}
|
|
189
|
-
function attachmentTypeFromExt(ext) {
|
|
190
|
-
if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.heic'].includes(ext))
|
|
191
|
-
return 'image';
|
|
192
|
-
if (['.mp4', '.mov', '.avi', '.mkv'].includes(ext))
|
|
193
|
-
return 'video';
|
|
194
|
-
return 'file';
|
|
195
|
-
}
|
|
196
|
-
function mimeTypeFromExt(ext) {
|
|
197
|
-
const map = {
|
|
198
|
-
'.png': 'image/png',
|
|
199
|
-
'.jpg': 'image/jpeg',
|
|
200
|
-
'.jpeg': 'image/jpeg',
|
|
201
|
-
'.gif': 'image/gif',
|
|
202
|
-
'.webp': 'image/webp',
|
|
203
|
-
'.heic': 'image/heic',
|
|
204
|
-
'.mp4': 'video/mp4',
|
|
205
|
-
'.mov': 'video/quicktime',
|
|
206
|
-
'.pdf': 'application/pdf',
|
|
207
|
-
'.txt': 'text/plain',
|
|
208
|
-
'.md': 'text/markdown',
|
|
209
|
-
'.csv': 'text/csv',
|
|
210
|
-
'.zip': 'application/zip',
|
|
211
|
-
};
|
|
212
|
-
return map[ext] || 'application/octet-stream';
|
|
213
|
-
}
|
|
214
|
-
function stableId(value) {
|
|
215
|
-
return crypto.createHash('sha256').update(value).digest('hex').slice(0, 24);
|
|
216
|
-
}
|
|
217
84
|
function resolveFlowScriptPath(scriptPath, workDir) {
|
|
218
85
|
const candidates = [
|
|
219
86
|
scriptPath,
|
|
@@ -8,7 +8,6 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
8
8
|
import { ChannelSecretRegistry } from './secret-registry.js';
|
|
9
9
|
import { probeMacWeChat, observedMessageFromProbe } from './wechat-rpa/macos.js';
|
|
10
10
|
import { runMacWeChatRpaFlow } from './wechat-rpa/macos-flow.js';
|
|
11
|
-
import { runWindowsWeChatRpaVisualFlow } from './wechat-rpa/windows-visual-flow.js';
|
|
12
11
|
import { normalizeWeChatRpaMessage, WeChatRpaDeduper, weChatRpaConversationId, } from './wechat-rpa/normalizer.js';
|
|
13
12
|
const DEFAULT_POLL_INTERVAL_MS = 5_000;
|
|
14
13
|
const DEFAULT_RECENT_LIMIT = 5;
|
|
@@ -159,7 +158,6 @@ export class WeChatRpaChannelAdapter {
|
|
|
159
158
|
noteManualReview(conn, pendingKey, reason);
|
|
160
159
|
return { status: 'manual-review', reason };
|
|
161
160
|
}
|
|
162
|
-
recordCloudOcrRuntime(conn, flow);
|
|
163
161
|
addTaskSummary(conn, {
|
|
164
162
|
status: 'sent',
|
|
165
163
|
runId: conn.lastRunId ?? null,
|
|
@@ -225,11 +223,6 @@ export class WeChatRpaChannelAdapter {
|
|
|
225
223
|
wechatRpaLastTracePath: conn.lastTracePath ?? null,
|
|
226
224
|
wechatRpaLastTraceSummary: conn.lastTraceSummary ?? null,
|
|
227
225
|
wechatRpaRecentTaskSummaries: conn.recentTaskSummaries,
|
|
228
|
-
wechatRpaLastCloudOcrAt: conn.lastCloudOcrAt ?? null,
|
|
229
|
-
wechatRpaLastCloudOcrPurpose: conn.lastCloudOcrPurpose ?? null,
|
|
230
|
-
wechatRpaLastCloudOcrRequestId: conn.lastCloudOcrRequestId ?? null,
|
|
231
|
-
wechatRpaLastCloudOcrImageHash: conn.lastCloudOcrImageHash ?? null,
|
|
232
|
-
wechatRpaLastCloudOcrUsage: conn.lastCloudOcrUsage ?? null,
|
|
233
226
|
};
|
|
234
227
|
}
|
|
235
228
|
ensureConnection(config) {
|
|
@@ -284,7 +277,6 @@ export class WeChatRpaChannelAdapter {
|
|
|
284
277
|
conn.runtimeState = 'syncing';
|
|
285
278
|
const observed = await this.readObservedMessages(conn.config, secret, (flow) => {
|
|
286
279
|
recordTraceRuntime(conn, flow);
|
|
287
|
-
recordCloudOcrRuntime(conn, flow);
|
|
288
280
|
if (!flow.interrupted)
|
|
289
281
|
return;
|
|
290
282
|
interrupted = true;
|
|
@@ -345,7 +337,6 @@ export class WeChatRpaChannelAdapter {
|
|
|
345
337
|
persistPendingReplyState(conn);
|
|
346
338
|
const flow = await runSendFlow(conn.config, secret, pending.conversationName, pending.skipText ? '' : pending.text, pending.attachmentPath, pending.key);
|
|
347
339
|
recordTraceRuntime(conn, flow);
|
|
348
|
-
recordCloudOcrRuntime(conn, flow);
|
|
349
340
|
const partial = applyPartialSendProgress(flow, {
|
|
350
341
|
text: pending.skipText ? '' : pending.text,
|
|
351
342
|
attachmentPath: pending.attachmentPath,
|
|
@@ -446,10 +437,8 @@ function isWeChatRpaTextMentioned(text, aliases) {
|
|
|
446
437
|
export function windowsVisualFlowHealth(secret, platform = process.platform) {
|
|
447
438
|
if (platform !== 'win32')
|
|
448
439
|
return { ok: false, message: 'WeChat RPA Windows visual flow requires Windows' };
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
return { ok: true, message: 'WeChat RPA Windows visual flow configured' };
|
|
440
|
+
void secret;
|
|
441
|
+
return { ok: false, message: 'WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it' };
|
|
453
442
|
}
|
|
454
443
|
async function readLabMessages(config, secret, onFlow) {
|
|
455
444
|
const api = await importWeChatRpaLabApi(config.workDir);
|
|
@@ -475,7 +464,6 @@ async function runLabReadFlow(api, secret, groupName) {
|
|
|
475
464
|
targetGroup: groupName,
|
|
476
465
|
policy: secret.forceForeground ? 'work' : 'polite',
|
|
477
466
|
limit: clampNumber(secret.recentLimit, DEFAULT_RECENT_LIMIT, 1, 50),
|
|
478
|
-
readMode: secret.readMode === 'hybrid-vlm' ? 'hybrid-vlm' : 'local-ocr',
|
|
479
467
|
};
|
|
480
468
|
const result = await api.runWechatRpaReadLatest(task);
|
|
481
469
|
const structuredMessages = Array.isArray(result.data?.structuredMessages) ? result.data.structuredMessages : [];
|
|
@@ -497,7 +485,6 @@ async function runLabReadFlow(api, secret, groupName) {
|
|
|
497
485
|
recentMessages: flowMessages,
|
|
498
486
|
newMessages: flowMessages,
|
|
499
487
|
screenshotPath: result.tracePath,
|
|
500
|
-
cloudOcrUsage: result.data?.hybrid?.usage,
|
|
501
488
|
error: labResultError(result),
|
|
502
489
|
};
|
|
503
490
|
}
|
|
@@ -630,7 +617,6 @@ async function readMacFlowMessages(config, secret, onFlow) {
|
|
|
630
617
|
idleSeconds: clampNumber(secret.idleSeconds, 15, 0, 3600),
|
|
631
618
|
recentLimit: clampNumber(secret.recentLimit, DEFAULT_RECENT_LIMIT, 0, 50),
|
|
632
619
|
downloadAttachmentsDir: resolveInboundAttachmentDir(config.workDir, secret),
|
|
633
|
-
cloudOcrMode: 'off',
|
|
634
620
|
});
|
|
635
621
|
onFlow?.(flow);
|
|
636
622
|
if (flow.interrupted)
|
|
@@ -644,26 +630,18 @@ async function readMacFlowMessages(config, secret, onFlow) {
|
|
|
644
630
|
return result;
|
|
645
631
|
}
|
|
646
632
|
async function readWindowsVisualFlowMessages(config, secret, onFlow) {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
continue;
|
|
660
|
-
for (const message of flow.newMessages ?? []) {
|
|
661
|
-
const observed = flowMessageToObservedMessage(name, message, secret);
|
|
662
|
-
if (observed)
|
|
663
|
-
result.push(observed);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
return result;
|
|
633
|
+
void config;
|
|
634
|
+
const groupName = configuredGroups(secret)[0] || '<unbound>';
|
|
635
|
+
onFlow?.({
|
|
636
|
+
ok: false,
|
|
637
|
+
groupName,
|
|
638
|
+
interrupted: true,
|
|
639
|
+
reason: 'windows-visual-flow-archived',
|
|
640
|
+
newMessages: [],
|
|
641
|
+
recentMessages: [],
|
|
642
|
+
error: 'WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it',
|
|
643
|
+
});
|
|
644
|
+
return [];
|
|
667
645
|
}
|
|
668
646
|
function flowMessageToObservedMessage(conversationName, message, secret) {
|
|
669
647
|
const text = String(message.text || '').trim();
|
|
@@ -684,16 +662,19 @@ function flowMessageToObservedMessage(conversationName, message, secret) {
|
|
|
684
662
|
}
|
|
685
663
|
async function runSendFlow(config, secret, conversationName, text, attachmentPath, dedupeKey) {
|
|
686
664
|
if (secret.source === 'windows-visual-flow') {
|
|
687
|
-
|
|
665
|
+
void config;
|
|
666
|
+
void text;
|
|
667
|
+
void attachmentPath;
|
|
668
|
+
void dedupeKey;
|
|
669
|
+
return {
|
|
670
|
+
ok: false,
|
|
688
671
|
groupName: conversationName,
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
cloudOcrMode: 'off',
|
|
696
|
-
});
|
|
672
|
+
interrupted: true,
|
|
673
|
+
reason: 'windows-visual-flow-archived',
|
|
674
|
+
newMessages: [],
|
|
675
|
+
recentMessages: [],
|
|
676
|
+
error: 'WeChat RPA Windows visual flow is archived; redesign Windows support before enabling it',
|
|
677
|
+
};
|
|
697
678
|
}
|
|
698
679
|
if (secret.source === 'wechat-rpa-lab') {
|
|
699
680
|
return runLabSendFlow(config, secret, conversationName, text, attachmentPath, dedupeKey);
|
|
@@ -709,11 +690,10 @@ async function runSendFlow(config, secret, conversationName, text, attachmentPat
|
|
|
709
690
|
idleSeconds: clampNumber(secret.idleSeconds, 15, 0, 3600),
|
|
710
691
|
recentLimit: clampNumber(secret.recentLimit, DEFAULT_RECENT_LIMIT, 0, 50),
|
|
711
692
|
downloadAttachmentsDir: resolveInboundAttachmentDir(config.workDir, secret),
|
|
712
|
-
cloudOcrMode: 'off',
|
|
713
693
|
});
|
|
714
694
|
}
|
|
715
695
|
function isFlowSource(source) {
|
|
716
|
-
return source === 'macos-flow' || source === '
|
|
696
|
+
return source === 'macos-flow' || source === 'wechat-rpa-lab';
|
|
717
697
|
}
|
|
718
698
|
function applyPartialSendProgress(flow, input) {
|
|
719
699
|
if (input.text && input.attachmentPath && flow.sentReplyObserved && !flow.sentAttachmentObserved) {
|
|
@@ -883,7 +863,6 @@ function noteInterruption(conn, flow, reason) {
|
|
|
883
863
|
conn.interruptionCooldownUntil = Date.now() + INTERRUPTION_COOLDOWN_MS;
|
|
884
864
|
conn.runtimeState = 'cooldown';
|
|
885
865
|
}
|
|
886
|
-
recordCloudOcrRuntime(conn, flow);
|
|
887
866
|
addTaskSummary(conn, {
|
|
888
867
|
status: 'interrupted',
|
|
889
868
|
runId: flow.rpaRunId || conn.lastRunId || null,
|
|
@@ -1041,10 +1020,3 @@ function clampNumber(value, fallback, min, max) {
|
|
|
1041
1020
|
return fallback;
|
|
1042
1021
|
return Math.min(max, Math.max(min, number));
|
|
1043
1022
|
}
|
|
1044
|
-
function recordCloudOcrRuntime(conn, flow) {
|
|
1045
|
-
void conn;
|
|
1046
|
-
void flow;
|
|
1047
|
-
// WeChat RPA production routing is local OCR only. Legacy cloud OCR payload
|
|
1048
|
-
// fields may still appear in old fixtures, but the channel must not publish
|
|
1049
|
-
// them as runtime status or invite a server OCR dependency back in.
|
|
1050
|
-
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
2
|
export declare function registerManagerCommand(program: Command): void;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// @test src/__tests__/manager-runtime.test.ts
|
|
3
3
|
// @test src/__tests__/manager-command.test.ts
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
-
import { Option } from 'commander';
|
|
6
5
|
import chalk from 'chalk';
|
|
7
6
|
import { readExternalAttachment } from './external-attachments.js';
|
|
8
7
|
function requireManagerContext() {
|
|
@@ -75,9 +74,6 @@ function printWeChatRpaStatus(channel) {
|
|
|
75
74
|
channel.wechatRpaLastRunAt ? `lastRun=${String(channel.wechatRpaLastRunAt)}` : '',
|
|
76
75
|
channel.wechatRpaLastMessageAt ? `lastMessage=${String(channel.wechatRpaLastMessageAt)}` : '',
|
|
77
76
|
channel.wechatRpaLastInterruptedAt ? `lastInterrupted=${String(channel.wechatRpaLastInterruptedAt)}` : '',
|
|
78
|
-
channel.wechatRpaLastCloudOcrAt ? `lastLocalOcr=${String(channel.wechatRpaLastCloudOcrAt)}` : '',
|
|
79
|
-
channel.wechatRpaLastCloudOcrPurpose ? `lastLocalOcrPurpose=${String(channel.wechatRpaLastCloudOcrPurpose)}` : '',
|
|
80
|
-
channel.wechatRpaLastCloudOcrRequestId ? `lastLocalOcrRequestId=${String(channel.wechatRpaLastCloudOcrRequestId)}` : '',
|
|
81
77
|
channel.wechatRpaLastError ? `lastError=${String(channel.wechatRpaLastError)}` : 'lastError=none',
|
|
82
78
|
].filter(Boolean);
|
|
83
79
|
console.log(fields.join('\n'));
|
|
@@ -353,20 +349,17 @@ export function registerManagerCommand(program) {
|
|
|
353
349
|
.option('--id <id>', 'Channel id')
|
|
354
350
|
.option('--name <name>', 'Channel display name')
|
|
355
351
|
.requiredOption('--enabled <true|false>', 'Whether the channel should be enabled')
|
|
356
|
-
.option('--group <name>', 'Bound WeChat
|
|
352
|
+
.option('--group <name>', 'Bound WeChat conversation name; pass once per Shennian conversation', collect, [])
|
|
357
353
|
.option('--can-reply <true|false>', 'Whether reply should be allowed')
|
|
358
|
-
.option('--source <macos-flow|
|
|
354
|
+
.option('--source <macos-flow|wechat-rpa-lab|macos-probe|fixture-jsonl>', 'WeChat RPA implementation source')
|
|
359
355
|
.option('--poll-interval-ms <n>', 'Polling interval in milliseconds')
|
|
360
|
-
.option('--recent-limit <n>', 'Recent message
|
|
356
|
+
.option('--recent-limit <n>', 'Recent message limit')
|
|
361
357
|
.option('--idle-seconds <n>', 'Minimum user idle seconds before foreground automation')
|
|
362
358
|
.option('--force-foreground <true|false>', 'Force WeChat foreground while syncing')
|
|
363
359
|
.option('--restore-previous <true|false>', 'Restore previous foreground app after syncing')
|
|
364
360
|
.option('--download-attachments <true|false>', 'Click and localize inbound attachment candidates')
|
|
365
361
|
.option('--download-attachments-dir <path>', 'Directory for localized inbound WeChat attachments')
|
|
366
362
|
.option('--flow-script-path <path>', 'Override macOS flow script path')
|
|
367
|
-
.addOption(new Option('--cloud-ocr-url <url>', 'Deprecated; ignored because WeChat RPA uses local-only OCR').hideHelp())
|
|
368
|
-
.addOption(new Option('--cloud-ocr-token <token>', 'Deprecated; ignored because WeChat RPA uses local-only OCR').hideHelp())
|
|
369
|
-
.addOption(new Option('--cloud-ocr-mode <off|fallback|always>', 'Deprecated; ignored because WeChat RPA uses local-only OCR').hideHelp())
|
|
370
363
|
.option('--json', 'Print JSON')
|
|
371
364
|
.action(async (opts) => {
|
|
372
365
|
if (opts.enabled === 'true' && opts.group.length > 1) {
|
package/dist/src/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const AUTO_UPGRADE_INITIAL_DELAY_MS = 30_000;
|
|
|
25
25
|
const AUTO_UPGRADE_POLL_INTERVAL_MS = 5 * 60_000;
|
|
26
26
|
import { getCachedAgentInfos, resolveAgentInfos } from './agents/model-registry.js';
|
|
27
27
|
import { initCliLogReporter, reportLog } from './log-reporter.js';
|
|
28
|
+
import { isNativeFusionEnabled } from './native-fusion/config.js';
|
|
28
29
|
import { NativeSessionFusionService } from './native-fusion/service.js';
|
|
29
30
|
import { startDaemonLogRetention } from './daemon-log.js';
|
|
30
31
|
const SHENNIAN_DIR = getShennianDir();
|
|
@@ -285,7 +286,7 @@ program
|
|
|
285
286
|
},
|
|
286
287
|
});
|
|
287
288
|
nativeFusion =
|
|
288
|
-
|
|
289
|
+
isNativeFusionEnabled()
|
|
289
290
|
? new NativeSessionFusionService(client)
|
|
290
291
|
: null;
|
|
291
292
|
const sessionManager = new SessionManager(client, nativeFusion, currentCliVersion);
|
|
@@ -665,9 +665,6 @@ export class ManagerRuntimeService {
|
|
|
665
665
|
downloadAttachments: body.downloadAttachments === undefined ? undefined : Boolean(body.downloadAttachments),
|
|
666
666
|
downloadAttachmentsDir: typeof body.downloadAttachmentsDir === 'string' ? body.downloadAttachmentsDir : undefined,
|
|
667
667
|
flowScriptPath: typeof body.flowScriptPath === 'string' ? body.flowScriptPath : undefined,
|
|
668
|
-
cloudOcrUrl: typeof body.cloudOcrUrl === 'string' ? body.cloudOcrUrl : undefined,
|
|
669
|
-
cloudOcrToken: typeof body.cloudOcrToken === 'string' ? body.cloudOcrToken : undefined,
|
|
670
|
-
cloudOcrMode: parseCloudOcrMode(body.cloudOcrMode),
|
|
671
668
|
});
|
|
672
669
|
this.registry.upsertManager({
|
|
673
670
|
...manager,
|
|
@@ -1004,9 +1001,6 @@ function mimeTypeFromExternalAttachment(attachment) {
|
|
|
1004
1001
|
return 'audio/*';
|
|
1005
1002
|
return 'application/octet-stream';
|
|
1006
1003
|
}
|
|
1007
|
-
function parseCloudOcrMode(value) {
|
|
1008
|
-
return value === 'off' || value === 'fallback' || value === 'always' ? value : undefined;
|
|
1009
|
-
}
|
|
1010
1004
|
function optionalNumber(value) {
|
|
1011
1005
|
const number = Number(value);
|
|
1012
1006
|
return Number.isFinite(number) ? number : undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isNativeFusionEnabled(env?: NodeJS.ProcessEnv): boolean;
|