shennian 0.2.75 → 0.2.77
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/scripts/wechat-rpa-confirmation.mjs +97 -0
- package/dist/scripts/wechat-rpa-download-candidates.mjs +105 -0
- package/dist/scripts/wechat-rpa-win-visual.mjs +707 -0
- package/dist/scripts/wechat-rpa-win.mjs +126 -0
- package/dist/src/agents/command-spec.js +24 -2
- package/dist/src/agents/external-channel-instructions.js +2 -0
- package/dist/src/channels/base.d.ts +1 -0
- package/dist/src/channels/runtime.d.ts +9 -2
- package/dist/src/channels/runtime.js +56 -6
- package/dist/src/channels/secret-registry.d.ts +1 -1
- package/dist/src/channels/wechat-rpa/macos-flow.d.ts +5 -0
- package/dist/src/channels/wechat-rpa/macos-flow.js +50 -2
- package/dist/src/channels/wechat-rpa/normalizer.d.ts +3 -0
- package/dist/src/channels/wechat-rpa/normalizer.js +16 -1
- package/dist/src/channels/wechat-rpa/windows-visual-flow.d.ts +36 -0
- package/dist/src/channels/wechat-rpa/windows-visual-flow.js +182 -0
- package/dist/src/channels/wechat-rpa.d.ts +13 -16
- package/dist/src/channels/wechat-rpa.js +186 -14
- package/dist/src/commands/external.js +10 -1
- package/dist/src/commands/manager.js +18 -0
- package/dist/src/manager/runtime.js +23 -6
- package/dist/src/session/handlers/chat.js +12 -7
- package/dist/src/session/queue.js +2 -0
- package/dist/src/session/types.d.ts +1 -0
- package/dist/src/upgrade/engine.d.ts +1 -0
- package/dist/src/upgrade/engine.js +10 -1
- package/package.json +5 -5
|
@@ -219,6 +219,7 @@ export function registerManagerCommand(program) {
|
|
|
219
219
|
.command('send')
|
|
220
220
|
.description('Send a message to the Manager-bound external channel')
|
|
221
221
|
.requiredOption('--text <text>', 'Message text')
|
|
222
|
+
.option('--reply-target <id>', 'Daemon-generated reply target; omitted means latest external message for this Manager')
|
|
222
223
|
.option('--idempotency-key <key>', 'Idempotency key')
|
|
223
224
|
.action(sendExternal);
|
|
224
225
|
external
|
|
@@ -226,11 +227,13 @@ export function registerManagerCommand(program) {
|
|
|
226
227
|
.description('Send an image file to the Manager-bound external channel')
|
|
227
228
|
.requiredOption('--path <path>', 'Image file path')
|
|
228
229
|
.option('--caption <text>', 'Optional text to send before the image')
|
|
230
|
+
.option('--reply-target <id>', 'Daemon-generated reply target; omitted means latest external message for this Manager')
|
|
229
231
|
.option('--idempotency-key <key>', 'Idempotency key')
|
|
230
232
|
.action(async (opts) => {
|
|
231
233
|
await sendExternal({
|
|
232
234
|
text: opts.caption,
|
|
233
235
|
attachment: readExternalAttachment(opts.path, 'image'),
|
|
236
|
+
replyTarget: opts.replyTarget,
|
|
234
237
|
idempotencyKey: opts.idempotencyKey,
|
|
235
238
|
});
|
|
236
239
|
});
|
|
@@ -239,11 +242,13 @@ export function registerManagerCommand(program) {
|
|
|
239
242
|
.description('Send a video file to the Manager-bound external channel')
|
|
240
243
|
.requiredOption('--path <path>', 'Video file path')
|
|
241
244
|
.option('--caption <text>', 'Optional text to send before the video')
|
|
245
|
+
.option('--reply-target <id>', 'Daemon-generated reply target; omitted means latest external message for this Manager')
|
|
242
246
|
.option('--idempotency-key <key>', 'Idempotency key')
|
|
243
247
|
.action(async (opts) => {
|
|
244
248
|
await sendExternal({
|
|
245
249
|
text: opts.caption,
|
|
246
250
|
attachment: readExternalAttachment(opts.path, 'video'),
|
|
251
|
+
replyTarget: opts.replyTarget,
|
|
247
252
|
idempotencyKey: opts.idempotencyKey,
|
|
248
253
|
});
|
|
249
254
|
});
|
|
@@ -252,11 +257,13 @@ export function registerManagerCommand(program) {
|
|
|
252
257
|
.description('Send a file to the Manager-bound external channel')
|
|
253
258
|
.requiredOption('--path <path>', 'File path')
|
|
254
259
|
.option('--caption <text>', 'Optional text to send before the file')
|
|
260
|
+
.option('--reply-target <id>', 'Daemon-generated reply target; omitted means latest external message for this Manager')
|
|
255
261
|
.option('--idempotency-key <key>', 'Idempotency key')
|
|
256
262
|
.action(async (opts) => {
|
|
257
263
|
await sendExternal({
|
|
258
264
|
text: opts.caption,
|
|
259
265
|
attachment: readExternalAttachment(opts.path, 'file'),
|
|
266
|
+
replyTarget: opts.replyTarget,
|
|
260
267
|
idempotencyKey: opts.idempotencyKey,
|
|
261
268
|
});
|
|
262
269
|
});
|
|
@@ -328,6 +335,17 @@ export function registerManagerCommand(program) {
|
|
|
328
335
|
else
|
|
329
336
|
printWeChatRpaStatus((result.channel ?? null));
|
|
330
337
|
});
|
|
338
|
+
wechatRpa
|
|
339
|
+
.command('sync')
|
|
340
|
+
.description('Run one immediate WeChat RPA sync and print the updated runtime status')
|
|
341
|
+
.option('--json', 'Print JSON')
|
|
342
|
+
.action(async (opts) => {
|
|
343
|
+
const result = await ipc('/wechat-rpa/channel/sync', {});
|
|
344
|
+
if (opts.json)
|
|
345
|
+
printJson(result);
|
|
346
|
+
else
|
|
347
|
+
printWeChatRpaStatus((result.channel ?? null));
|
|
348
|
+
});
|
|
331
349
|
wechatRpa
|
|
332
350
|
.command('upsert')
|
|
333
351
|
.description('Create or update a local WeChat RPA channel binding')
|
|
@@ -12,6 +12,7 @@ import { ChannelRuntime } from '../channels/runtime.js';
|
|
|
12
12
|
import { splitExternalReplyText } from '../channels/reply-split.js';
|
|
13
13
|
import { resolveShennianPath } from '../config/index.js';
|
|
14
14
|
import { buildExternalChannelInstructions } from '../agents/external-channel-instructions.js';
|
|
15
|
+
const MAX_MANAGER_IPC_BODY_BYTES = Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES || 2 * 1024 * 1024);
|
|
15
16
|
let singleton = null;
|
|
16
17
|
export function setManagerRuntimeService(service) {
|
|
17
18
|
singleton = service;
|
|
@@ -71,18 +72,19 @@ function parseExternalReplyAttachment(value) {
|
|
|
71
72
|
const localPath = String(record.localPath || '');
|
|
72
73
|
const url = String(record.url || '');
|
|
73
74
|
const size = Number(record.size || 0);
|
|
75
|
+
if (dataBase64)
|
|
76
|
+
throw new Error('Manager IPC external attachments must use localPath or url; dataBase64 is not accepted');
|
|
74
77
|
if (kind !== 'image' && kind !== 'video' && kind !== 'file')
|
|
75
78
|
return undefined;
|
|
76
79
|
if (!name || !mimeType || !Number.isFinite(size) || size < 0)
|
|
77
80
|
return undefined;
|
|
78
|
-
if (!
|
|
81
|
+
if (!localPath && !url)
|
|
79
82
|
return undefined;
|
|
80
83
|
return {
|
|
81
84
|
kind,
|
|
82
85
|
name,
|
|
83
86
|
mimeType,
|
|
84
87
|
size,
|
|
85
|
-
...(dataBase64 ? { dataBase64 } : {}),
|
|
86
88
|
...(localPath ? { localPath } : {}),
|
|
87
89
|
...(url ? { url } : {}),
|
|
88
90
|
};
|
|
@@ -147,8 +149,15 @@ function compactWorkerTranscript(rawMessages, limit) {
|
|
|
147
149
|
}
|
|
148
150
|
async function readJson(req) {
|
|
149
151
|
const chunks = [];
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
let total = 0;
|
|
153
|
+
for await (const chunk of req) {
|
|
154
|
+
const buffer = Buffer.from(chunk);
|
|
155
|
+
total += buffer.byteLength;
|
|
156
|
+
if (Number.isFinite(MAX_MANAGER_IPC_BODY_BYTES) && MAX_MANAGER_IPC_BODY_BYTES > 0 && total > MAX_MANAGER_IPC_BODY_BYTES) {
|
|
157
|
+
throw new Error(`Manager IPC request body is too large. Max: ${MAX_MANAGER_IPC_BODY_BYTES} bytes.`);
|
|
158
|
+
}
|
|
159
|
+
chunks.push(buffer);
|
|
160
|
+
}
|
|
152
161
|
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
153
162
|
return raw ? JSON.parse(raw) : {};
|
|
154
163
|
}
|
|
@@ -668,6 +677,12 @@ export class ManagerRuntimeService {
|
|
|
668
677
|
json(res, 200, { ok: true, channel });
|
|
669
678
|
return;
|
|
670
679
|
}
|
|
680
|
+
if (url.pathname === '/wechat-rpa/channel/sync') {
|
|
681
|
+
const { channel, messages } = await this.channelRuntime.syncManagerWeChatRpaChannel(managerSessionId);
|
|
682
|
+
this.broadcastManagerChannelStatus(managerSessionId);
|
|
683
|
+
json(res, 200, { ok: true, channel, messages });
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
671
686
|
json(res, 404, { ok: false, error: `Unknown manager IPC path: ${url.pathname}` });
|
|
672
687
|
}
|
|
673
688
|
catch (err) {
|
|
@@ -757,7 +772,8 @@ ${message || worker.summary || '(无可见摘要)'}
|
|
|
757
772
|
const modelId = config?.modelId || manager?.modelId || '';
|
|
758
773
|
const attachmentInputs = externalAttachmentsForAgent(event.attachments);
|
|
759
774
|
const attachmentSummary = externalAttachmentSummary(event.attachments, event.text);
|
|
760
|
-
const
|
|
775
|
+
const visibleReplyTarget = event.replyTarget ? `回复目标:${event.replyTarget}` : '';
|
|
776
|
+
const visibleBody = [event.text, attachmentSummary, visibleReplyTarget].filter(Boolean).join('\n');
|
|
761
777
|
const visibleMessage = visibleBody
|
|
762
778
|
? `外部消息 / ${event.sender.name || event.sender.id}\n${visibleBody}`
|
|
763
779
|
: `外部消息 / ${event.sender.name || event.sender.id}`;
|
|
@@ -794,6 +810,7 @@ ${message || worker.summary || '(无可见摘要)'}
|
|
|
794
810
|
origin: 'external',
|
|
795
811
|
attachments: input.attachments,
|
|
796
812
|
externalChannel: input.externalChannel ?? null,
|
|
813
|
+
replyTarget: input.replyTarget,
|
|
797
814
|
},
|
|
798
815
|
});
|
|
799
816
|
}
|
|
@@ -879,7 +896,7 @@ function parseWeChatRpaGroups(value) {
|
|
|
879
896
|
.filter((item) => item.name);
|
|
880
897
|
}
|
|
881
898
|
function parseWeChatRpaSource(value) {
|
|
882
|
-
return value === 'macos-flow' || value === 'macos-probe' || value === 'fixture-jsonl' ? value : undefined;
|
|
899
|
+
return value === 'macos-flow' || value === 'macos-probe' || value === 'windows-visual-flow' || value === 'fixture-jsonl' ? value : undefined;
|
|
883
900
|
}
|
|
884
901
|
function externalAttachmentsForAgent(attachments) {
|
|
885
902
|
const result = attachments
|
|
@@ -142,7 +142,7 @@ function externalChannelEnabled(channel) {
|
|
|
142
142
|
function managedProviderEnv(agentType) {
|
|
143
143
|
return buildManagedAgentEnv(agentType);
|
|
144
144
|
}
|
|
145
|
-
function externalChannelEnv(sessionId, channel) {
|
|
145
|
+
function externalChannelEnv(sessionId, channel, replyTarget) {
|
|
146
146
|
if (!externalChannelEnabled(channel))
|
|
147
147
|
return {};
|
|
148
148
|
const service = getManagerRuntimeService();
|
|
@@ -151,15 +151,16 @@ function externalChannelEnv(sessionId, channel) {
|
|
|
151
151
|
...injected,
|
|
152
152
|
SHENNIAN_EXTERNAL_SESSION_ID: sessionId,
|
|
153
153
|
SHENNIAN_MANAGER_SESSION_ID: sessionId,
|
|
154
|
+
...(replyTarget?.trim() ? { SHENNIAN_EXTERNAL_REPLY_TARGET: replyTarget.trim() } : {}),
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
|
-
function configureAdapterForSession(adapter, sessionId, agentType, channel) {
|
|
157
|
+
function configureAdapterForSession(adapter, sessionId, agentType, channel, replyTarget) {
|
|
157
158
|
adapter.configure?.({
|
|
158
159
|
sessionId,
|
|
159
160
|
externalChannel: channel ?? null,
|
|
160
161
|
env: {
|
|
161
162
|
...managedProviderEnv(agentType),
|
|
162
|
-
...externalChannelEnv(sessionId, channel),
|
|
163
|
+
...externalChannelEnv(sessionId, channel, replyTarget),
|
|
163
164
|
},
|
|
164
165
|
});
|
|
165
166
|
}
|
|
@@ -357,12 +358,12 @@ async function disposeSession(session) {
|
|
|
357
358
|
session.adapter.removeAllListeners();
|
|
358
359
|
await session.adapter.stop().catch(() => { });
|
|
359
360
|
}
|
|
360
|
-
async function createActiveSession(runtime, sessionId, agentType, resolvedWorkDir, incomingAgentSid, externalChannel) {
|
|
361
|
+
async function createActiveSession(runtime, sessionId, agentType, resolvedWorkDir, incomingAgentSid, externalChannel, externalReplyTarget) {
|
|
361
362
|
runtime.evictIdleSessions();
|
|
362
363
|
const adapter = createAgent(agentType);
|
|
363
364
|
if (!adapter)
|
|
364
365
|
throw new Error(`Unsupported agent: ${agentType}`);
|
|
365
|
-
configureAdapterForSession(adapter, sessionId, agentType, externalChannel);
|
|
366
|
+
configureAdapterForSession(adapter, sessionId, agentType, externalChannel, externalReplyTarget);
|
|
366
367
|
await adapter.start(sessionId, resolvedWorkDir, incomingAgentSid);
|
|
367
368
|
const session = {
|
|
368
369
|
adapter,
|
|
@@ -374,9 +375,10 @@ async function createActiveSession(runtime, sessionId, agentType, resolvedWorkDi
|
|
|
374
375
|
nextEventSeq: 0,
|
|
375
376
|
pendingTextEvent: null,
|
|
376
377
|
externalChannel: externalChannel ?? null,
|
|
378
|
+
externalReplyTarget: externalReplyTarget ?? null,
|
|
377
379
|
externalChannelEnv: {
|
|
378
380
|
...managedProviderEnv(agentType),
|
|
379
|
-
...externalChannelEnv(sessionId, externalChannel),
|
|
381
|
+
...externalChannelEnv(sessionId, externalChannel, externalReplyTarget),
|
|
380
382
|
},
|
|
381
383
|
};
|
|
382
384
|
runtime.sessions.set(sessionId, session);
|
|
@@ -412,6 +414,9 @@ export async function handleChatSend(runtime, req) {
|
|
|
412
414
|
const replyId = responseId || req.id;
|
|
413
415
|
mergeProjectedSessions(sessionListProjection);
|
|
414
416
|
const incomingExternalChannel = normalizeExternalChannel(req.params.externalChannel);
|
|
417
|
+
const incomingReplyTarget = typeof req.params.replyTarget === 'string'
|
|
418
|
+
? req.params.replyTarget.trim()
|
|
419
|
+
: '';
|
|
415
420
|
if (!sessionId || !text) {
|
|
416
421
|
runtime.processedReqIds.delete(req.id);
|
|
417
422
|
runtime.client.sendRes({ type: 'res', id: replyId, ok: false, error: 'sessionId and text are required' });
|
|
@@ -467,7 +472,7 @@ export async function handleChatSend(runtime, req) {
|
|
|
467
472
|
}
|
|
468
473
|
if (!session) {
|
|
469
474
|
try {
|
|
470
|
-
session = await createActiveSession(runtime, sessionId, requestedAgentType, resolvedWorkDir, incomingAgentSid, incomingExternalChannel);
|
|
475
|
+
session = await createActiveSession(runtime, sessionId, requestedAgentType, resolvedWorkDir, incomingAgentSid, incomingExternalChannel, incomingReplyTarget);
|
|
471
476
|
}
|
|
472
477
|
catch (err) {
|
|
473
478
|
const message = err instanceof Error && err.message.startsWith('Unsupported agent:')
|
|
@@ -63,6 +63,7 @@ function queueMessageFromParams(params) {
|
|
|
63
63
|
clientMessageId: params.clientMessageId ?? null,
|
|
64
64
|
attachments: normalizeAttachments(params.attachments),
|
|
65
65
|
externalChannel: params.externalChannel ?? null,
|
|
66
|
+
replyTarget: params.replyTarget ?? null,
|
|
66
67
|
origin: params.origin,
|
|
67
68
|
createdAt: timestamp,
|
|
68
69
|
updatedAt: timestamp,
|
|
@@ -301,6 +302,7 @@ export class ChatQueueManager {
|
|
|
301
302
|
clientMessageId: message.clientMessageId ?? message.id,
|
|
302
303
|
attachments: message.attachments,
|
|
303
304
|
externalChannel: message.externalChannel,
|
|
305
|
+
replyTarget: message.replyTarget,
|
|
304
306
|
waitForDispatch: true,
|
|
305
307
|
},
|
|
306
308
|
});
|
|
@@ -54,6 +54,7 @@ export declare function fetchLatestVersion(): Promise<string>;
|
|
|
54
54
|
* 'major' — major differs
|
|
55
55
|
*/
|
|
56
56
|
export declare function compareVersions(current: string, latest: string): 'none' | 'patch' | 'minor' | 'major';
|
|
57
|
+
export declare function copyPackageRuntimeFiles(src: string, dest: string): void;
|
|
57
58
|
export declare function readUpgradeAttempt(): UpgradeAttempt | null;
|
|
58
59
|
export declare function writeUpgradeAttempt(attempt: UpgradeAttempt): void;
|
|
59
60
|
export declare function clearUpgradeAttempt(): void;
|
|
@@ -86,8 +86,9 @@ function getGlobalBinScript() {
|
|
|
86
86
|
function backupVersion(version) {
|
|
87
87
|
const pkgDir = getGlobalPkgDir();
|
|
88
88
|
const dest = path.join(BACKUP_DIR, version);
|
|
89
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
89
90
|
fs.mkdirSync(dest, { recursive: true });
|
|
90
|
-
|
|
91
|
+
copyPackageRuntimeFiles(pkgDir, dest);
|
|
91
92
|
}
|
|
92
93
|
function restoreVersion(version) {
|
|
93
94
|
const src = path.join(BACKUP_DIR, version);
|
|
@@ -96,6 +97,14 @@ function restoreVersion(version) {
|
|
|
96
97
|
const pkgDir = getGlobalPkgDir();
|
|
97
98
|
fs.cpSync(src, pkgDir, { recursive: true, force: true });
|
|
98
99
|
}
|
|
100
|
+
export function copyPackageRuntimeFiles(src, dest) {
|
|
101
|
+
for (const entry of ['package.json', 'README.md', 'dist']) {
|
|
102
|
+
const from = path.join(src, entry);
|
|
103
|
+
if (!fs.existsSync(from))
|
|
104
|
+
continue;
|
|
105
|
+
fs.cpSync(from, path.join(dest, entry), { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
99
108
|
function cleanOldBackups() {
|
|
100
109
|
try {
|
|
101
110
|
if (!fs.existsSync(BACKUP_DIR))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shennian",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.77",
|
|
4
4
|
"description": "Shennian — AI Agent Control Plane CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@mariozechner/pi-agent-core": "^0.64.0",
|
|
38
38
|
"@sinclair/typebox": "^0.34.49",
|
|
39
|
+
"@shennian/wire": "^0.1.5",
|
|
39
40
|
"chalk": "^5.4.1",
|
|
40
41
|
"commander": "^13.1.0",
|
|
41
42
|
"qrcode-terminal": "^0.12.0",
|
|
42
|
-
"ws": "^8.18.1"
|
|
43
|
-
"@shennian/wire": "0.1.5"
|
|
43
|
+
"ws": "^8.18.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^20",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"typescript": "^5.9.3"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
|
-
"build": "tsc && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
|
|
54
|
-
"build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
|
|
53
|
+
"build": "tsc && node scripts/copy-wechat-rpa-assets.mjs && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
|
|
54
|
+
"build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json && node scripts/copy-wechat-rpa-assets.mjs && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
|
|
55
55
|
"dev": "tsc --watch"
|
|
56
56
|
}
|
|
57
57
|
}
|