shennian 0.2.72 → 0.2.74
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/command-spec.js +19 -12
- package/dist/src/agents/external-channel-instructions.d.ts +3 -1
- package/dist/src/agents/external-channel-instructions.js +73 -15
- package/dist/src/channels/base.d.ts +62 -9
- package/dist/src/channels/runtime.d.ts +43 -10
- package/dist/src/channels/runtime.js +300 -14
- package/dist/src/channels/secret-registry.d.ts +17 -1
- package/dist/src/channels/websocket.d.ts +3 -0
- package/dist/src/channels/websocket.js +39 -2
- package/dist/src/channels/wechat-rpa/macos-flow.d.ts +77 -0
- package/dist/src/channels/wechat-rpa/macos-flow.js +254 -0
- package/dist/src/channels/wechat-rpa/macos.d.ts +11 -0
- package/dist/src/channels/wechat-rpa/macos.js +63 -0
- package/dist/src/channels/wechat-rpa/normalizer.d.ts +42 -0
- package/dist/src/channels/wechat-rpa/normalizer.js +99 -0
- package/dist/src/channels/wechat-rpa.d.ts +51 -0
- package/dist/src/channels/wechat-rpa.js +587 -0
- package/dist/src/channels/wecom.d.ts +3 -0
- package/dist/src/channels/wecom.js +43 -1
- package/dist/src/commands/external-attachments.d.ts +1 -1
- package/dist/src/commands/external-attachments.js +2 -3
- package/dist/src/commands/external.js +19 -1
- package/dist/src/commands/manager.js +109 -0
- package/dist/src/manager/prompt.d.ts +1 -1
- package/dist/src/manager/prompt.js +1 -11
- package/dist/src/manager/runtime.d.ts +2 -10
- package/dist/src/manager/runtime.js +197 -33
- package/dist/src/native-fusion/service.js +7 -0
- package/dist/src/session/archive-zip.d.ts +10 -0
- package/dist/src/session/archive-zip.js +220 -0
- package/dist/src/session/handlers/agent-config.js +85 -6
- package/dist/src/session/handlers/chat.js +58 -2
- package/dist/src/session/handlers/fs.d.ts +1 -0
- package/dist/src/session/handlers/fs.js +57 -1
- package/dist/src/session/manager.js +4 -1
- package/package.json +10 -9
|
@@ -5,8 +5,13 @@ import chalk from 'chalk';
|
|
|
5
5
|
import { resolveShennianPath } from '../config/index.js';
|
|
6
6
|
import { readExternalAttachment } from './external-attachments.js';
|
|
7
7
|
function loadManagerIpcFromRuntimeFile() {
|
|
8
|
+
const filePath = resolveShennianPath('runtime', 'manager-ipc.json');
|
|
8
9
|
try {
|
|
9
|
-
const parsed = JSON.parse(fs.readFileSync(
|
|
10
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
11
|
+
const pid = Number(parsed.pid);
|
|
12
|
+
if (Number.isInteger(pid) && pid > 0 && !processExists(pid)) {
|
|
13
|
+
return { error: `Manager IPC runtime file is stale for PID ${pid}. Restart the desktop daemon and retry.` };
|
|
14
|
+
}
|
|
10
15
|
return {
|
|
11
16
|
url: typeof parsed.url === 'string' ? parsed.url : undefined,
|
|
12
17
|
token: typeof parsed.token === 'string' ? parsed.token : undefined,
|
|
@@ -20,6 +25,10 @@ function requireExternalContext(explicitSessionId) {
|
|
|
20
25
|
const runtimeIpc = !process.env.SHENNIAN_MANAGER_IPC_URL || !process.env.SHENNIAN_MANAGER_IPC_TOKEN
|
|
21
26
|
? loadManagerIpcFromRuntimeFile()
|
|
22
27
|
: {};
|
|
28
|
+
if (runtimeIpc.error) {
|
|
29
|
+
console.error(chalk.red(`✗ ${runtimeIpc.error}`));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
23
32
|
const url = process.env.SHENNIAN_MANAGER_IPC_URL || runtimeIpc.url;
|
|
24
33
|
const token = process.env.SHENNIAN_MANAGER_IPC_TOKEN || runtimeIpc.token;
|
|
25
34
|
const sessionId = explicitSessionId?.trim() || process.env.SHENNIAN_EXTERNAL_SESSION_ID || process.env.SHENNIAN_MANAGER_SESSION_ID;
|
|
@@ -29,6 +38,15 @@ function requireExternalContext(explicitSessionId) {
|
|
|
29
38
|
}
|
|
30
39
|
return { url, token, sessionId };
|
|
31
40
|
}
|
|
41
|
+
function processExists(pid) {
|
|
42
|
+
try {
|
|
43
|
+
process.kill(pid, 0);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
32
50
|
async function sendExternal(input) {
|
|
33
51
|
const ctx = requireExternalContext(input.sessionId);
|
|
34
52
|
const response = await fetch(`${ctx.url}/external/reply`, {
|
|
@@ -39,6 +39,48 @@ function readMessage(opts) {
|
|
|
39
39
|
return fs.readFileSync(opts.messageFile, 'utf-8');
|
|
40
40
|
return opts.message ?? '';
|
|
41
41
|
}
|
|
42
|
+
function collect(value, previous = []) {
|
|
43
|
+
previous.push(value);
|
|
44
|
+
return previous;
|
|
45
|
+
}
|
|
46
|
+
function parseBool(value) {
|
|
47
|
+
if (value === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
return value === 'true';
|
|
50
|
+
}
|
|
51
|
+
function parseNumber(value) {
|
|
52
|
+
const number = Number(value);
|
|
53
|
+
return Number.isFinite(number) ? number : undefined;
|
|
54
|
+
}
|
|
55
|
+
function printWeChatRpaStatus(channel) {
|
|
56
|
+
if (!channel) {
|
|
57
|
+
console.log('not configured');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const groups = Array.isArray(channel.wechatRpaGroups)
|
|
61
|
+
? channel.wechatRpaGroups
|
|
62
|
+
.map((group) => typeof group === 'object' && group ? String(group.name || '').trim() : '')
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.join(', ')
|
|
65
|
+
: '';
|
|
66
|
+
const fields = [
|
|
67
|
+
`enabled=${Boolean(channel.enabled)}`,
|
|
68
|
+
`connected=${Boolean(channel.tokenConfigured ?? channel.enabled)}`,
|
|
69
|
+
channel.wechatRpaSource ? `source=${String(channel.wechatRpaSource)}` : '',
|
|
70
|
+
groups ? `groups=${groups}` : 'groups=',
|
|
71
|
+
channel.pollIntervalMs ? `poll=${String(channel.pollIntervalMs)}ms` : '',
|
|
72
|
+
channel.wechatRpaRuntimeState ? `state=${String(channel.wechatRpaRuntimeState)}` : '',
|
|
73
|
+
Number.isFinite(channel.wechatRpaPendingReplyCount) ? `pendingReplies=${String(channel.wechatRpaPendingReplyCount)}` : '',
|
|
74
|
+
channel.wechatRpaLastRunAt ? `lastRun=${String(channel.wechatRpaLastRunAt)}` : '',
|
|
75
|
+
channel.wechatRpaLastMessageAt ? `lastMessage=${String(channel.wechatRpaLastMessageAt)}` : '',
|
|
76
|
+
channel.wechatRpaLastInterruptedAt ? `lastInterrupted=${String(channel.wechatRpaLastInterruptedAt)}` : '',
|
|
77
|
+
channel.wechatRpaLastCloudOcrAt ? `lastCloudOcr=${String(channel.wechatRpaLastCloudOcrAt)}` : '',
|
|
78
|
+
channel.wechatRpaLastCloudOcrPurpose ? `lastCloudOcrPurpose=${String(channel.wechatRpaLastCloudOcrPurpose)}` : '',
|
|
79
|
+
channel.wechatRpaLastCloudOcrRequestId ? `lastCloudOcrRequestId=${String(channel.wechatRpaLastCloudOcrRequestId)}` : '',
|
|
80
|
+
channel.wechatRpaLastError ? `lastError=${String(channel.wechatRpaLastError)}` : 'lastError=none',
|
|
81
|
+
].filter(Boolean);
|
|
82
|
+
console.log(fields.join('\n'));
|
|
83
|
+
}
|
|
42
84
|
export function registerManagerCommand(program) {
|
|
43
85
|
const manager = program.command('manager').description('Manager Agent local tools');
|
|
44
86
|
const sessions = manager.command('sessions').description('Manage same-project worker sessions');
|
|
@@ -263,4 +305,71 @@ export function registerManagerCommand(program) {
|
|
|
263
305
|
else
|
|
264
306
|
printJson(result.channel ?? null);
|
|
265
307
|
});
|
|
308
|
+
const wechatRpa = manager.command('wechat-rpa').description('Local WeChat RPA channel config');
|
|
309
|
+
wechatRpa
|
|
310
|
+
.command('get')
|
|
311
|
+
.description('Get local WeChat RPA channel config')
|
|
312
|
+
.option('--json', 'Print JSON')
|
|
313
|
+
.action(async (opts) => {
|
|
314
|
+
const result = await ipc('/wechat-rpa/channel/get', {});
|
|
315
|
+
if (opts.json)
|
|
316
|
+
printJson(result);
|
|
317
|
+
else
|
|
318
|
+
printJson(result.channel ?? null);
|
|
319
|
+
});
|
|
320
|
+
wechatRpa
|
|
321
|
+
.command('status')
|
|
322
|
+
.description('Print local WeChat RPA channel runtime status')
|
|
323
|
+
.option('--json', 'Print JSON')
|
|
324
|
+
.action(async (opts) => {
|
|
325
|
+
const result = await ipc('/wechat-rpa/channel/get', {});
|
|
326
|
+
if (opts.json)
|
|
327
|
+
printJson(result);
|
|
328
|
+
else
|
|
329
|
+
printWeChatRpaStatus((result.channel ?? null));
|
|
330
|
+
});
|
|
331
|
+
wechatRpa
|
|
332
|
+
.command('upsert')
|
|
333
|
+
.description('Create or update a local WeChat RPA channel binding')
|
|
334
|
+
.option('--id <id>', 'Channel id')
|
|
335
|
+
.option('--name <name>', 'Channel display name')
|
|
336
|
+
.requiredOption('--enabled <true|false>', 'Whether the channel should be enabled')
|
|
337
|
+
.option('--group <name>', 'WeChat group name; repeat for multiple groups', collect, [])
|
|
338
|
+
.option('--can-reply <true|false>', 'Whether reply should be allowed')
|
|
339
|
+
.option('--poll-interval-ms <n>', 'Polling interval in milliseconds')
|
|
340
|
+
.option('--recent-limit <n>', 'Recent message OCR debug limit')
|
|
341
|
+
.option('--idle-seconds <n>', 'Minimum user idle seconds before foreground automation')
|
|
342
|
+
.option('--force-foreground <true|false>', 'Force WeChat foreground while syncing')
|
|
343
|
+
.option('--restore-previous <true|false>', 'Restore previous foreground app after syncing')
|
|
344
|
+
.option('--download-attachments <true|false>', 'Click and localize inbound attachment candidates')
|
|
345
|
+
.option('--download-attachments-dir <path>', 'Directory for localized inbound WeChat attachments')
|
|
346
|
+
.option('--flow-script-path <path>', 'Override macOS flow script path')
|
|
347
|
+
.option('--cloud-ocr-url <url>', 'Shennian server OCR fallback URL')
|
|
348
|
+
.option('--cloud-ocr-token <token>', 'Shennian user/channel token for OCR fallback')
|
|
349
|
+
.option('--cloud-ocr-mode <off|fallback|always>', 'Cloud OCR mode')
|
|
350
|
+
.option('--json', 'Print JSON')
|
|
351
|
+
.action(async (opts) => {
|
|
352
|
+
const result = await ipc('/wechat-rpa/channel/upsert', {
|
|
353
|
+
id: opts.id,
|
|
354
|
+
name: opts.name,
|
|
355
|
+
enabled: opts.enabled === 'true',
|
|
356
|
+
groups: opts.group.map((name) => ({ name })),
|
|
357
|
+
canReply: parseBool(opts.canReply),
|
|
358
|
+
pollIntervalMs: parseNumber(opts.pollIntervalMs),
|
|
359
|
+
recentLimit: parseNumber(opts.recentLimit),
|
|
360
|
+
idleSeconds: parseNumber(opts.idleSeconds),
|
|
361
|
+
forceForeground: parseBool(opts.forceForeground),
|
|
362
|
+
noRestore: opts.restorePrevious === undefined ? undefined : opts.restorePrevious !== 'true',
|
|
363
|
+
downloadAttachments: parseBool(opts.downloadAttachments),
|
|
364
|
+
downloadAttachmentsDir: opts.downloadAttachmentsDir,
|
|
365
|
+
flowScriptPath: opts.flowScriptPath,
|
|
366
|
+
cloudOcrUrl: opts.cloudOcrUrl,
|
|
367
|
+
cloudOcrToken: opts.cloudOcrToken,
|
|
368
|
+
cloudOcrMode: opts.cloudOcrMode,
|
|
369
|
+
});
|
|
370
|
+
if (opts.json)
|
|
371
|
+
printJson(result);
|
|
372
|
+
else
|
|
373
|
+
printJson(result.channel ?? null);
|
|
374
|
+
});
|
|
266
375
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const MANAGER_SYSTEM_PROMPT = "\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76EE\u7684\u7BA1\u7406\u8005\u3002\n\n\u4F60\u7684\u804C\u8D23\uFF1A\n- \u7406\u89E3\u7528\u6237\u76EE\u6807\u3002\n- \u62C6\u89E3\u4EFB\u52A1\u3002\n- \u521B\u5EFA\u3001\u6307\u6D3E\u3001\u89C2\u5BDF\u548C\u505C\u6B62\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u4E0B\u7684 worker Agent session\u3002\n- \u6C47\u603B worker \u7ED3\u679C\u3002\n- \u5224\u65AD\u662F\u5426\u9700\u8981\u7EE7\u7EED\u7B49\u5F85\u3001\u8C03\u6574\u5B89\u6392\u3001\u8BE2\u95EE\u7528\u6237\u6216\u9A8C\u6536\u3002\n- \u5728\u9879\u76EE .shennian/ \u76EE\u5F55\u4E0B\u7EF4\u62A4\u5FC5\u8981\u7684\u8BA1\u5212\u3001\u8BB0\u5F55\u548C\u9879\u76EE\u8BB0\u5FC6\u3002\n\n\u4F60\u7684\u8FB9\u754C\uFF1A\n- \u4E0D\u8981\u628A\u81EA\u5DF1\u5F53\u4F5C\u4E3B\u8981\u6267\u884C\u8005\u3002\n- \u4E0D\u8981\u76F4\u63A5\u7F16\u8F91\u4E1A\u52A1\u4EE3\u7801\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u4EB2\u81EA\u6267\u884C\u3002\n- \u53EF\u4EE5\u8BFB\u53D6\u6587\u4EF6\u3001\u641C\u7D22\u9879\u76EE\u548C\u68C0\u67E5\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u4FBF\u505A\u5224\u65AD\u3002\n- \u9700\u8981\u4FEE\u6539\u4EE3\u7801\u3001\u8FD0\u884C\u6D4B\u8BD5\u3001\u8C03\u7814\u65B9\u6848\u65F6\uFF0C\u4F18\u5148\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002\n- \u6BCF\u6B21\u6536\u5230\u65B0\u4EFB\u52A1\u3001\u8865\u5145\u8981\u6C42\u3001\u7EA0\u504F\u6216\u5916\u90E8\u6D88\u606F\u65F6\uFF0C\u5148\u7528 sessions list \u67E5\u770B\u5F53\u524D\u540C\u9879\u76EE worker\uFF1B\u5982\u679C\u53EF\u80FD\u76F8\u5173\uFF0C\u518D\u7528 sessions read \u8BFB\u53D6\u5FC5\u8981\u6458\u8981\u540E\u5224\u65AD\u662F\u5426\u590D\u7528\u3002\n- \u5982\u679C\u5DF2\u6709 worker \u6B63\u5728\u5904\u7406\u540C\u4E00\u76EE\u6807\u3001\u540C\u4E00\u529F\u80FD\u533A\u3001\u540C\u4E00\u6587\u4EF6\u8303\u56F4\u6216\u540C\u4E00\u95EE\u9898\u94FE\u8DEF\uFF0C\u4F18\u5148\u7528 sessions send \u628A\u65B0\u8981\u6C42\u53D1\u7ED9\u8FD9\u4E2A worker\uFF1B\u5373\u4F7F worker \u6B63\u5FD9\u4E5F\u53EF\u4EE5\u53D1\u9001\uFF0C\u9ED8\u8BA4\u4F1A\u8FDB\u5165\u672C\u673A\u961F\u5217\uFF0C\u4E0D\u8981\u56E0\u4E3A\u5B83\u5FD9\u5C31\u65B0\u5EFA worker\u3002\n- \u53EA\u6709\u6CA1\u6709\u76F8\u5173 worker\u3001\u73B0\u6709 worker \u5DF2\u660E\u663E\u4E0D\u9002\u5408\u7EE7\u7EED\u63A8\u8FDB\u3001\u6216\u4EFB\u52A1\u9700\u8981\u5E76\u884C\u62C6\u5206\u7ED9\u4E0D\u540C\u4E13\u957F\u65F6\uFF0C\u624D\u521B\u5EFA\u65B0\u7684 worker\u3002\n- \u521B\u5EFA\u6216\u6307\u6D3E worker \u540E\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u5F53\u573A\u7EE7\u7EED\u8C03\u5EA6\uFF0C\u5426\u5219\u56DE\u590D\u7528\u6237\u5DF2\u5B89\u6392\u5E76\u7ED3\u675F\u5F53\u524D turn\uFF1B\u4E0D\u8981\u4E3B\u52A8\u8F6E\u8BE2 worker \u72B6\u6001\uFF0C\u795E\u5FF5\u4F1A\u5728 worker \u7EC8\u6001\u6216\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u91CD\u65B0\u5524\u9192\u4F60\u3002\n- sessions read \u8FD4\u56DE\u7684\u662F\u7ED9\u7BA1\u7406\u8005\u770B\u7684\u7B80\u6D01\u8FDB\u5C55\u3001\u5DE5\u5177\u6458\u8981\u548C\u6700\u7EC8\u7ED3\u679C\uFF0C\u4E0D\u662F\u539F\u59CB\u6D41\u5F0F token\uFF1B\u4E0D\u8981\u8981\u6C42\u8BFB\u53D6\u6216\u8F6C\u8FF0\u5B8C\u6574\u6D41\u5F0F\u65E5\u5FD7\u3002\n- \u53EA\u80FD\u7BA1\u7406\u4E0E\u4F60\u5904\u4E8E\u540C\u4E00\u53F0\u673A\u5668\u3001\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u7684\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u8DE8\u673A\u5668\u6216\u8DE8\u9879\u76EE\u8C03\u5EA6\u3002\n- \u4E0D\u8981\u65E0\u9650\u5FAA\u73AF\uFF1B\u6CA1\u6709\u660E\u786E\u4E0B\u4E00\u6B65\u65F6\u8BE2\u95EE\u7528\u6237\u6216\u7ED3\u675F\u5F53\u524D turn \u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u3002\n- \u4E0D\u8981\u81EA\u5DF1\u8BBE\u7F6E\u5B9A\u65F6\u5524\u9192\uFF1B\u795E\u5FF5\u4F1A\u5728\u7528\u6237\u6D88\u606F\u3001worker \u7EC8\u6001\u6216 worker \u957F\u8FD0\u884C\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u5524\u9192\u4F60\u3002\n- \u5916\u90E8\u6D88\u606F\u901A\u9053\u4E8B\u4EF6\u4F1A\u50CF\u666E\u901A\u7528\u6237\u6D88\u606F\u4E00\u6837\u9001\u8FBE\uFF0C\u683C\u5F0F\u7C7B\u4F3C\u201C\u5916\u90E8\u6D88\u606F / \u53D1\u9001\u4EBA\u201D\u540E\u8DDF\u6D88\u606F\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u5408\u5E76\u6D88\u606F\uFF0C\u4E5F\u53EF\u80FD\u5305\u542B\u56FE\u7247\u3001\u89C6\u9891\u6216\u6587\u4EF6 URL\u3002\n- \
|
|
1
|
+
export declare const MANAGER_SYSTEM_PROMPT = "\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76EE\u7684\u7BA1\u7406\u8005\u3002\n\n\u4F60\u7684\u804C\u8D23\uFF1A\n- \u7406\u89E3\u7528\u6237\u76EE\u6807\u3002\n- \u62C6\u89E3\u4EFB\u52A1\u3002\n- \u521B\u5EFA\u3001\u6307\u6D3E\u3001\u89C2\u5BDF\u548C\u505C\u6B62\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u4E0B\u7684 worker Agent session\u3002\n- \u6C47\u603B worker \u7ED3\u679C\u3002\n- \u5224\u65AD\u662F\u5426\u9700\u8981\u7EE7\u7EED\u7B49\u5F85\u3001\u8C03\u6574\u5B89\u6392\u3001\u8BE2\u95EE\u7528\u6237\u6216\u9A8C\u6536\u3002\n- \u5728\u9879\u76EE .shennian/ \u76EE\u5F55\u4E0B\u7EF4\u62A4\u5FC5\u8981\u7684\u8BA1\u5212\u3001\u8BB0\u5F55\u548C\u9879\u76EE\u8BB0\u5FC6\u3002\n\n\u4F60\u7684\u8FB9\u754C\uFF1A\n- \u4E0D\u8981\u628A\u81EA\u5DF1\u5F53\u4F5C\u4E3B\u8981\u6267\u884C\u8005\u3002\n- \u4E0D\u8981\u76F4\u63A5\u7F16\u8F91\u4E1A\u52A1\u4EE3\u7801\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u4EB2\u81EA\u6267\u884C\u3002\n- \u53EF\u4EE5\u8BFB\u53D6\u6587\u4EF6\u3001\u641C\u7D22\u9879\u76EE\u548C\u68C0\u67E5\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u4FBF\u505A\u5224\u65AD\u3002\n- \u9700\u8981\u4FEE\u6539\u4EE3\u7801\u3001\u8FD0\u884C\u6D4B\u8BD5\u3001\u8C03\u7814\u65B9\u6848\u65F6\uFF0C\u4F18\u5148\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002\n- \u6BCF\u6B21\u6536\u5230\u65B0\u4EFB\u52A1\u3001\u8865\u5145\u8981\u6C42\u3001\u7EA0\u504F\u6216\u5916\u90E8\u6D88\u606F\u65F6\uFF0C\u5148\u7528 sessions list \u67E5\u770B\u5F53\u524D\u540C\u9879\u76EE worker\uFF1B\u5982\u679C\u53EF\u80FD\u76F8\u5173\uFF0C\u518D\u7528 sessions read \u8BFB\u53D6\u5FC5\u8981\u6458\u8981\u540E\u5224\u65AD\u662F\u5426\u590D\u7528\u3002\n- \u5982\u679C\u5DF2\u6709 worker \u6B63\u5728\u5904\u7406\u540C\u4E00\u76EE\u6807\u3001\u540C\u4E00\u529F\u80FD\u533A\u3001\u540C\u4E00\u6587\u4EF6\u8303\u56F4\u6216\u540C\u4E00\u95EE\u9898\u94FE\u8DEF\uFF0C\u4F18\u5148\u7528 sessions send \u628A\u65B0\u8981\u6C42\u53D1\u7ED9\u8FD9\u4E2A worker\uFF1B\u5373\u4F7F worker \u6B63\u5FD9\u4E5F\u53EF\u4EE5\u53D1\u9001\uFF0C\u9ED8\u8BA4\u4F1A\u8FDB\u5165\u672C\u673A\u961F\u5217\uFF0C\u4E0D\u8981\u56E0\u4E3A\u5B83\u5FD9\u5C31\u65B0\u5EFA worker\u3002\n- \u53EA\u6709\u6CA1\u6709\u76F8\u5173 worker\u3001\u73B0\u6709 worker \u5DF2\u660E\u663E\u4E0D\u9002\u5408\u7EE7\u7EED\u63A8\u8FDB\u3001\u6216\u4EFB\u52A1\u9700\u8981\u5E76\u884C\u62C6\u5206\u7ED9\u4E0D\u540C\u4E13\u957F\u65F6\uFF0C\u624D\u521B\u5EFA\u65B0\u7684 worker\u3002\n- \u521B\u5EFA\u6216\u6307\u6D3E worker \u540E\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u5F53\u573A\u7EE7\u7EED\u8C03\u5EA6\uFF0C\u5426\u5219\u56DE\u590D\u7528\u6237\u5DF2\u5B89\u6392\u5E76\u7ED3\u675F\u5F53\u524D turn\uFF1B\u4E0D\u8981\u4E3B\u52A8\u8F6E\u8BE2 worker \u72B6\u6001\uFF0C\u795E\u5FF5\u4F1A\u5728 worker \u7EC8\u6001\u6216\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u91CD\u65B0\u5524\u9192\u4F60\u3002\n- sessions read \u8FD4\u56DE\u7684\u662F\u7ED9\u7BA1\u7406\u8005\u770B\u7684\u7B80\u6D01\u8FDB\u5C55\u3001\u5DE5\u5177\u6458\u8981\u548C\u6700\u7EC8\u7ED3\u679C\uFF0C\u4E0D\u662F\u539F\u59CB\u6D41\u5F0F token\uFF1B\u4E0D\u8981\u8981\u6C42\u8BFB\u53D6\u6216\u8F6C\u8FF0\u5B8C\u6574\u6D41\u5F0F\u65E5\u5FD7\u3002\n- \u53EA\u80FD\u7BA1\u7406\u4E0E\u4F60\u5904\u4E8E\u540C\u4E00\u53F0\u673A\u5668\u3001\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u7684\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u8DE8\u673A\u5668\u6216\u8DE8\u9879\u76EE\u8C03\u5EA6\u3002\n- \u4E0D\u8981\u65E0\u9650\u5FAA\u73AF\uFF1B\u6CA1\u6709\u660E\u786E\u4E0B\u4E00\u6B65\u65F6\u8BE2\u95EE\u7528\u6237\u6216\u7ED3\u675F\u5F53\u524D turn \u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u3002\n- \u4E0D\u8981\u81EA\u5DF1\u8BBE\u7F6E\u5B9A\u65F6\u5524\u9192\uFF1B\u795E\u5FF5\u4F1A\u5728\u7528\u6237\u6D88\u606F\u3001worker \u7EC8\u6001\u6216 worker \u957F\u8FD0\u884C\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u5524\u9192\u4F60\u3002\n- \u5916\u90E8\u6D88\u606F\u901A\u9053\u4E8B\u4EF6\u4F1A\u50CF\u666E\u901A\u7528\u6237\u6D88\u606F\u4E00\u6837\u9001\u8FBE\uFF0C\u683C\u5F0F\u7C7B\u4F3C\u201C\u5916\u90E8\u6D88\u606F / \u53D1\u9001\u4EBA\u201D\u540E\u8DDF\u6D88\u606F\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u5408\u5E76\u6D88\u606F\uFF0C\u4E5F\u53EF\u80FD\u5305\u542B\u56FE\u7247\u3001\u89C6\u9891\u6216\u6587\u4EF6 URL\u3002\n- \u5916\u90E8\u6D88\u606F\u662F\u5426\u9700\u8981\u56DE\u590D\u3001\u8FFD\u95EE\u3001\u5FFD\u7565\u3001\u8F6C\u4EA4\u5185\u90E8\u8D1F\u8D23\u4EBA\uFF0C\u6216\u521B\u5EFA/\u6307\u6D3E worker\uFF0C\u7531\u4F60\u6839\u636E\u9879\u76EE\u4E0A\u4E0B\u6587\u663E\u5F0F\u5224\u65AD\u3002\n- \u5BF9\u5916\u4F60\u662F\u5F53\u524D\u9879\u76EE\u7684\u9879\u76EE\u7ECF\u7406\uFF0C\u4E0D\u8981\u81EA\u79F0\u795E\u5FF5\u3001Manager Agent \u6216 worker\uFF0C\u4E5F\u4E0D\u8981\u89E3\u91CA\u5185\u90E8\u8C03\u5EA6\u673A\u5236\uFF1B\u53EA\u5728\u9700\u8981\u65F6\u7528\u201C\u6211\u8FD9\u8FB9/\u6211\u4EEC\u8FD9\u8FB9\u201D\u6C9F\u901A\u3002\n- \u4E0D\u8981\u628A\u6240\u6709\u7EC6\u8282\u585E\u8FDB\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF1B\u9700\u8981\u957F\u671F\u4FDD\u5B58\u7684\u4FE1\u606F\u5199\u5230\u9879\u76EE .shennian/ \u4E0B\u3002\n\n\u9700\u8981\u7BA1\u7406 worker \u6216\u5916\u90E8\u901A\u9053\u65F6\uFF0C\u4F7F\u7528\u672C\u5730\u547D\u4EE4\uFF1A\n- shennian manager sessions list --json\n- shennian manager sessions start --agent <codex|claude|gemini|cursor|opencode|pi|custom:name> --workdir <path> --message <text>\n- shennian manager sessions send --session-id <id> --message <text>\n- shennian manager sessions send --session-id <id> --message <text> --direct\n- shennian manager sessions queue list --session-id <id> --json\n- shennian manager sessions queue edit --session-id <id> --message-id <queueMessageId> --message <text>\n- shennian manager sessions queue delete --session-id <id> --message-id <queueMessageId>\n- shennian manager sessions stop --session-id <id>\uFF08\u7EC8\u6B62\u6B63\u5728\u8FD0\u884C\u7684 worker\uFF1B\u4E5F\u53EF\u7528 terminate/kill \u522B\u540D\uFF09\n- shennian manager sessions read --session-id <id> --limit 200 --json\n- shennian manager memory path\n\n\u53EF\u521B\u5EFA\u7684 worker Agent \u5305\u62EC Codex\u3001Claude Code\u3001Gemini\u3001Cursor\u3001opencode\u3001Nian\uFF0C\u4EE5\u53CA\u672C\u673A custom agent\uFF08custom:<name>\uFF09\u3002\u9ED8\u8BA4\u7528 sessions send \u6392\u961F\u53D1\u9001 worker \u6D88\u606F\uFF1Aworker \u6B63\u5FD9\u65F6\u6D88\u606F\u4F1A\u5728\u672C\u673A daemon \u961F\u5217\u91CC\u7B49\u5F85\uFF0Cworker \u7A7A\u95F2\u65F6\u81EA\u52A8\u6267\u884C\u3002\u961F\u5217\u91CC\u7684\u672A\u6267\u884C\u6D88\u606F\u53EF\u4EE5 list/edit/delete\uFF1B\u5DF2\u7ECF\u5F00\u59CB\u6267\u884C\u7684\u6D88\u606F\u4E0D\u80FD\u7F16\u8F91\u6216\u5220\u9664\uFF0C\u53EA\u80FD stop \u540E\u91CD\u65B0\u53D1\u9001\u3002\u53EA\u6709\u660E\u786E\u9700\u8981\u6253\u65AD\u987A\u5E8F\u65F6\u624D\u4F7F\u7528 --direct\u3002\n\n\u8FD9\u4E9B\u547D\u4EE4\u5DF2\u7ECF\u7531\u795E\u5FF5\u6CE8\u5165\u5F53\u524D Manager \u8EAB\u4EFD\u548C\u540C\u9879\u76EE\u6743\u9650\u8FB9\u754C\u3002\u4E0D\u8981\u5C1D\u8BD5\u4F2A\u9020 Manager session id\u3002";
|
|
2
2
|
export declare function buildManagerPrompt(userText: string): string;
|
|
@@ -24,14 +24,8 @@ export const MANAGER_SYSTEM_PROMPT = `你是项目经理,是当前项目的管
|
|
|
24
24
|
- 不要无限循环;没有明确下一步时询问用户或结束当前 turn 等待系统事件。
|
|
25
25
|
- 不要自己设置定时唤醒;神念会在用户消息、worker 终态或 worker 长运行健康摘要到来时唤醒你。
|
|
26
26
|
- 外部消息通道事件会像普通用户消息一样送达,格式类似“外部消息 / 发送人”后跟消息内容,可能是合并消息,也可能包含图片、视频或文件 URL。
|
|
27
|
+
- 外部消息是否需要回复、追问、忽略、转交内部负责人,或创建/指派 worker,由你根据项目上下文显式判断。
|
|
27
28
|
- 对外你是当前项目的项目经理,不要自称神念、Manager Agent 或 worker,也不要解释内部调度机制;只在需要时用“我这边/我们这边”沟通。
|
|
28
|
-
- 对外回复必须像真人聊天:短回复一条发完;内容较多时按自然段拆成 2-4 条连续消息,每条只讲一个完整主题。
|
|
29
|
-
- 避免把超过 300-500 字的内容塞进单条消息;不要使用 Markdown、编号列表、项目符号或字面 \\n。
|
|
30
|
-
- 外部消息与当前项目无关时可以忽略;需要较长处理时,先简短回复“收到,我先处理/安排一下”,再创建或指派 worker。
|
|
31
|
-
- 向外部群发文字一律调用 shennian manager external send --text "<消息内容>"
|
|
32
|
-
- 向外部群发图片调用 shennian manager external send-image --path "<图片绝对路径>" --caption "<可选说明>"
|
|
33
|
-
- 向外部群发视频调用 shennian manager external send-video --path "<视频绝对路径>" --caption "<可选说明>"
|
|
34
|
-
- 向外部群发文件调用 shennian manager external send-file --path "<文件绝对路径>" --caption "<可选说明>"
|
|
35
29
|
- 不要把所有细节塞进对话上下文;需要长期保存的信息写到项目 .shennian/ 下。
|
|
36
30
|
|
|
37
31
|
需要管理 worker 或外部通道时,使用本地命令:
|
|
@@ -45,10 +39,6 @@ export const MANAGER_SYSTEM_PROMPT = `你是项目经理,是当前项目的管
|
|
|
45
39
|
- shennian manager sessions stop --session-id <id>(终止正在运行的 worker;也可用 terminate/kill 别名)
|
|
46
40
|
- shennian manager sessions read --session-id <id> --limit 200 --json
|
|
47
41
|
- shennian manager memory path
|
|
48
|
-
- shennian manager external send --text <text>
|
|
49
|
-
- shennian manager external send-image --path <path> --caption <text>
|
|
50
|
-
- shennian manager external send-video --path <path> --caption <text>
|
|
51
|
-
- shennian manager external send-file --path <path> --caption <text>
|
|
52
42
|
|
|
53
43
|
可创建的 worker Agent 包括 Codex、Claude Code、Gemini、Cursor、opencode、Nian,以及本机 custom agent(custom:<name>)。默认用 sessions send 排队发送 worker 消息:worker 正忙时消息会在本机 daemon 队列里等待,worker 空闲时自动执行。队列里的未执行消息可以 list/edit/delete;已经开始执行的消息不能编辑或删除,只能 stop 后重新发送。只有明确需要打断顺序时才使用 --direct。
|
|
54
44
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentEvent, AgentAdapter } from '../agents/adapter.js';
|
|
2
|
-
import { type AgentType, type ReqFrame } from '@shennian/wire';
|
|
2
|
+
import { type AgentType, type ExternalChannelSessionStatus, type ReqFrame } from '@shennian/wire';
|
|
3
3
|
import { ManagerRegistry } from './registry.js';
|
|
4
4
|
import type { SessionManagerRuntime } from '../session/types.js';
|
|
5
5
|
import { ChannelRuntime } from '../channels/runtime.js';
|
|
@@ -59,15 +59,7 @@ export declare class ManagerRuntimeService {
|
|
|
59
59
|
private interruptAndResumeManager;
|
|
60
60
|
bindManagerAdapterEvents(sessionId: string, adapter: AgentAdapter): void;
|
|
61
61
|
updateManagerStatus(sessionId: string, status: 'idle' | 'running' | 'interrupting'): void;
|
|
62
|
-
getExternalChannelStatus(managerSessionId: string):
|
|
63
|
-
configured: boolean;
|
|
64
|
-
connected: boolean;
|
|
65
|
-
type?: string;
|
|
66
|
-
channelId?: string;
|
|
67
|
-
name?: string;
|
|
68
|
-
canReply?: boolean;
|
|
69
|
-
systemPrompt?: string;
|
|
70
|
-
} | null;
|
|
62
|
+
getExternalChannelStatus(managerSessionId: string): ExternalChannelSessionStatus | null;
|
|
71
63
|
getManagerExternalChannelSystemPrompt(managerSessionId: string): string;
|
|
72
64
|
}
|
|
73
65
|
export {};
|
|
@@ -11,6 +11,7 @@ import { readMessages } from '../session/store.js';
|
|
|
11
11
|
import { ChannelRuntime } from '../channels/runtime.js';
|
|
12
12
|
import { splitExternalReplyText } from '../channels/reply-split.js';
|
|
13
13
|
import { resolveShennianPath } from '../config/index.js';
|
|
14
|
+
import { buildExternalChannelInstructions } from '../agents/external-channel-instructions.js';
|
|
14
15
|
let singleton = null;
|
|
15
16
|
export function setManagerRuntimeService(service) {
|
|
16
17
|
singleton = service;
|
|
@@ -67,12 +68,24 @@ function parseExternalReplyAttachment(value) {
|
|
|
67
68
|
const name = String(record.name || '');
|
|
68
69
|
const mimeType = String(record.mimeType || '');
|
|
69
70
|
const dataBase64 = String(record.dataBase64 || '');
|
|
71
|
+
const localPath = String(record.localPath || '');
|
|
72
|
+
const url = String(record.url || '');
|
|
70
73
|
const size = Number(record.size || 0);
|
|
71
74
|
if (kind !== 'image' && kind !== 'video' && kind !== 'file')
|
|
72
75
|
return undefined;
|
|
73
|
-
if (!name || !mimeType || !
|
|
76
|
+
if (!name || !mimeType || !Number.isFinite(size) || size < 0)
|
|
74
77
|
return undefined;
|
|
75
|
-
|
|
78
|
+
if (!dataBase64 && !localPath && !url)
|
|
79
|
+
return undefined;
|
|
80
|
+
return {
|
|
81
|
+
kind,
|
|
82
|
+
name,
|
|
83
|
+
mimeType,
|
|
84
|
+
size,
|
|
85
|
+
...(dataBase64 ? { dataBase64 } : {}),
|
|
86
|
+
...(localPath ? { localPath } : {}),
|
|
87
|
+
...(url ? { url } : {}),
|
|
88
|
+
};
|
|
76
89
|
}
|
|
77
90
|
function compactWorkerTranscript(rawMessages, limit) {
|
|
78
91
|
const chronological = [...rawMessages].sort((a, b) => a.ts - b.ts);
|
|
@@ -221,6 +234,7 @@ export class ManagerRuntimeService {
|
|
|
221
234
|
fs.writeFileSync(filePath, JSON.stringify({
|
|
222
235
|
url: this.ipcUrl,
|
|
223
236
|
token: this.ipcToken,
|
|
237
|
+
pid: process.pid,
|
|
224
238
|
updatedAt: new Date().toISOString(),
|
|
225
239
|
}, null, 2), { mode: 0o600 });
|
|
226
240
|
fs.chmodSync(filePath, 0o600);
|
|
@@ -536,45 +550,52 @@ export class ManagerRuntimeService {
|
|
|
536
550
|
const text = String(body.text || '');
|
|
537
551
|
const attachment = parseExternalReplyAttachment(body.attachment);
|
|
538
552
|
const idempotencyKey = String(body.idempotencyKey || randomUUID());
|
|
553
|
+
const explicitChannelId = String(body.channelId || '');
|
|
554
|
+
const explicitConversationId = String(body.conversationId || '');
|
|
555
|
+
const defaultTarget = !replyTarget && (!explicitChannelId || !explicitConversationId)
|
|
556
|
+
? await this.channelRuntime.getDefaultReplyTarget(managerSessionId).catch(() => null)
|
|
557
|
+
: null;
|
|
558
|
+
const channelId = replyTarget?.channelId || explicitChannelId || defaultTarget?.channelId || '';
|
|
559
|
+
const conversationId = replyTarget?.conversationId || explicitConversationId || defaultTarget?.conversationId || '';
|
|
560
|
+
if (channelId && this.channelRuntime.getChannelById(channelId)) {
|
|
561
|
+
if (!conversationId)
|
|
562
|
+
throw new Error('No external channel target is available for this Manager');
|
|
563
|
+
const result = await this.channelRuntime.reply({
|
|
564
|
+
managerSessionId,
|
|
565
|
+
channelId,
|
|
566
|
+
conversationId,
|
|
567
|
+
messageId: replyTarget?.messageId ?? undefined,
|
|
568
|
+
text,
|
|
569
|
+
attachment,
|
|
570
|
+
idempotencyKey,
|
|
571
|
+
});
|
|
572
|
+
json(res, result.ok ? 200 : 400, result);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
let relayResult;
|
|
539
576
|
try {
|
|
540
|
-
|
|
577
|
+
relayResult = await this.sendManagedWeComReply({
|
|
541
578
|
managerSessionId,
|
|
542
579
|
text,
|
|
543
580
|
attachment,
|
|
544
581
|
idempotencyKey,
|
|
545
582
|
});
|
|
546
|
-
if (relayResult.ok) {
|
|
547
|
-
json(res, 200, { ok: true, payload: relayResult.payload });
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (!shouldFallbackToLocalChannel(relayResult.error || '')) {
|
|
551
|
-
json(res, 400, { ok: false, error: relayResult.error || 'External send failed' });
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
583
|
}
|
|
555
584
|
catch (error) {
|
|
556
|
-
|
|
585
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
586
|
+
if (!shouldFallbackToLocalChannel(message))
|
|
557
587
|
throw error;
|
|
558
|
-
}
|
|
588
|
+
relayResult = { ok: false, error: message };
|
|
559
589
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const result = await this.channelRuntime.reply({
|
|
570
|
-
managerSessionId,
|
|
571
|
-
channelId,
|
|
572
|
-
conversationId,
|
|
573
|
-
messageId: replyTarget?.messageId ?? undefined,
|
|
574
|
-
text,
|
|
575
|
-
idempotencyKey,
|
|
576
|
-
});
|
|
577
|
-
json(res, result.ok ? 200 : 400, result);
|
|
590
|
+
if (relayResult.ok) {
|
|
591
|
+
json(res, 200, { ok: true, payload: relayResult.payload });
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
if (!shouldFallbackToLocalChannel(relayResult.error || '') || !channelId || !conversationId) {
|
|
595
|
+
json(res, 400, { ok: false, error: relayResult.error || 'External send failed' });
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
json(res, 400, { ok: false, error: `No local external channel is configured for ${channelId}` });
|
|
578
599
|
return;
|
|
579
600
|
}
|
|
580
601
|
if (url.pathname === '/channel/get') {
|
|
@@ -607,6 +628,46 @@ export class ManagerRuntimeService {
|
|
|
607
628
|
json(res, 200, { ok: true, channel });
|
|
608
629
|
return;
|
|
609
630
|
}
|
|
631
|
+
if (url.pathname === '/wechat-rpa/channel/get') {
|
|
632
|
+
json(res, 200, {
|
|
633
|
+
ok: true,
|
|
634
|
+
channel: this.channelRuntime.getManagerChannel(managerSessionId, 'wechat-rpa', { includeSecret: true }),
|
|
635
|
+
});
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (url.pathname === '/wechat-rpa/channel/upsert') {
|
|
639
|
+
if (!manager)
|
|
640
|
+
throw new Error('Manager runtime is not registered');
|
|
641
|
+
const channel = await this.channelRuntime.upsertManagerWeChatRpaChannel({
|
|
642
|
+
id: String(body.id || `wechat-rpa:${managerSessionId}`),
|
|
643
|
+
managerSessionId,
|
|
644
|
+
workDir: manager.workDir,
|
|
645
|
+
name: typeof body.name === 'string' ? body.name : undefined,
|
|
646
|
+
enabled: Boolean(body.enabled),
|
|
647
|
+
groups: parseWeChatRpaGroups(body.groups),
|
|
648
|
+
canReply: body.canReply === undefined ? undefined : Boolean(body.canReply),
|
|
649
|
+
systemPrompt: typeof body.systemPrompt === 'string' ? body.systemPrompt : undefined,
|
|
650
|
+
source: parseWeChatRpaSource(body.source),
|
|
651
|
+
pollIntervalMs: optionalNumber(body.pollIntervalMs),
|
|
652
|
+
recentLimit: optionalNumber(body.recentLimit),
|
|
653
|
+
idleSeconds: optionalNumber(body.idleSeconds),
|
|
654
|
+
forceForeground: body.forceForeground === undefined ? undefined : Boolean(body.forceForeground),
|
|
655
|
+
noRestore: body.noRestore === undefined ? undefined : Boolean(body.noRestore),
|
|
656
|
+
downloadAttachments: body.downloadAttachments === undefined ? undefined : Boolean(body.downloadAttachments),
|
|
657
|
+
downloadAttachmentsDir: typeof body.downloadAttachmentsDir === 'string' ? body.downloadAttachmentsDir : undefined,
|
|
658
|
+
flowScriptPath: typeof body.flowScriptPath === 'string' ? body.flowScriptPath : undefined,
|
|
659
|
+
cloudOcrUrl: typeof body.cloudOcrUrl === 'string' ? body.cloudOcrUrl : undefined,
|
|
660
|
+
cloudOcrToken: typeof body.cloudOcrToken === 'string' ? body.cloudOcrToken : undefined,
|
|
661
|
+
cloudOcrMode: parseCloudOcrMode(body.cloudOcrMode),
|
|
662
|
+
});
|
|
663
|
+
this.registry.upsertManager({
|
|
664
|
+
...manager,
|
|
665
|
+
status: manager.status,
|
|
666
|
+
});
|
|
667
|
+
this.broadcastManagerChannelStatus(managerSessionId);
|
|
668
|
+
json(res, 200, { ok: true, channel });
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
610
671
|
json(res, 404, { ok: false, error: `Unknown manager IPC path: ${url.pathname}` });
|
|
611
672
|
}
|
|
612
673
|
catch (err) {
|
|
@@ -687,12 +748,19 @@ ${message || worker.summary || '(无可见摘要)'}
|
|
|
687
748
|
handleExternalMessage(managerSessionId, event) {
|
|
688
749
|
const config = this.channelRuntime.getChannelById(event.channelId)
|
|
689
750
|
?? this.channelRuntime.getManagerChannel(managerSessionId, event.channelType);
|
|
751
|
+
const externalChannel = this.channelRuntime.getChannelStatusById(event.channelId)
|
|
752
|
+
?? this.channelRuntime.getManagerChannelStatus(managerSessionId);
|
|
690
753
|
const manager = this.registry.getManager(managerSessionId);
|
|
691
754
|
const agentType = (config?.agentType || (manager ? 'manager' : 'codex'));
|
|
692
755
|
const workDir = config?.workDir || manager?.workDir || process.cwd();
|
|
693
756
|
const agentSessionId = config?.agentSessionId ?? manager?.agentSessionId ?? null;
|
|
694
757
|
const modelId = config?.modelId || manager?.modelId || '';
|
|
695
|
-
const
|
|
758
|
+
const attachmentInputs = externalAttachmentsForAgent(event.attachments);
|
|
759
|
+
const attachmentSummary = externalAttachmentSummary(event.attachments, event.text);
|
|
760
|
+
const visibleBody = [event.text, attachmentSummary].filter(Boolean).join('\n');
|
|
761
|
+
const visibleMessage = visibleBody
|
|
762
|
+
? `外部消息 / ${event.sender.name || event.sender.id}\n${visibleBody}`
|
|
763
|
+
: `外部消息 / ${event.sender.name || event.sender.id}`;
|
|
696
764
|
this.registry.createReplyTarget({
|
|
697
765
|
managerSessionId,
|
|
698
766
|
channelId: event.channelId,
|
|
@@ -706,6 +774,8 @@ ${message || worker.summary || '(无可见摘要)'}
|
|
|
706
774
|
agentSessionId,
|
|
707
775
|
modelId,
|
|
708
776
|
text: visibleMessage,
|
|
777
|
+
attachments: attachmentInputs,
|
|
778
|
+
externalChannel,
|
|
709
779
|
replyTarget: event.replyTarget,
|
|
710
780
|
});
|
|
711
781
|
}
|
|
@@ -722,6 +792,8 @@ ${message || worker.summary || '(无可见摘要)'}
|
|
|
722
792
|
agentSessionId: input.agentSessionId,
|
|
723
793
|
modelId: input.modelId,
|
|
724
794
|
origin: 'external',
|
|
795
|
+
attachments: input.attachments,
|
|
796
|
+
externalChannel: input.externalChannel ?? null,
|
|
725
797
|
},
|
|
726
798
|
});
|
|
727
799
|
}
|
|
@@ -793,8 +865,100 @@ ${message || worker.summary || '(无可见摘要)'}
|
|
|
793
865
|
}
|
|
794
866
|
getManagerExternalChannelSystemPrompt(managerSessionId) {
|
|
795
867
|
return this.channelRuntime
|
|
796
|
-
.
|
|
868
|
+
.listManagerExternalChannels(managerSessionId)
|
|
869
|
+
.map((channel) => buildExternalChannelInstructions(channel, undefined, managerSessionId, 'manager'))
|
|
797
870
|
.join('\n\n')
|
|
798
871
|
.trim();
|
|
799
872
|
}
|
|
800
873
|
}
|
|
874
|
+
function parseWeChatRpaGroups(value) {
|
|
875
|
+
if (!Array.isArray(value))
|
|
876
|
+
return [];
|
|
877
|
+
return value
|
|
878
|
+
.map((item) => ({ name: String(item?.name || '').trim() }))
|
|
879
|
+
.filter((item) => item.name);
|
|
880
|
+
}
|
|
881
|
+
function parseWeChatRpaSource(value) {
|
|
882
|
+
return value === 'macos-flow' || value === 'macos-probe' || value === 'fixture-jsonl' ? value : undefined;
|
|
883
|
+
}
|
|
884
|
+
function externalAttachmentsForAgent(attachments) {
|
|
885
|
+
const result = attachments
|
|
886
|
+
.map((attachment) => externalAttachmentForAgent(attachment))
|
|
887
|
+
.filter((item) => item != null);
|
|
888
|
+
return result.length ? result : undefined;
|
|
889
|
+
}
|
|
890
|
+
function externalAttachmentSummary(attachments, existingText) {
|
|
891
|
+
const text = existingText || '';
|
|
892
|
+
const lines = attachments
|
|
893
|
+
.filter((attachment) => !externalAttachmentForAgent(attachment))
|
|
894
|
+
.map((attachment) => {
|
|
895
|
+
const label = attachment.name || attachment.type || 'attachment';
|
|
896
|
+
const status = externalAttachmentUnavailableStatus(attachment);
|
|
897
|
+
return `附件:${attachment.type || 'file'} ${label} (${status})`;
|
|
898
|
+
})
|
|
899
|
+
.filter((line) => !text.includes(line));
|
|
900
|
+
return lines.join('\n');
|
|
901
|
+
}
|
|
902
|
+
function externalAttachmentForAgent(attachment) {
|
|
903
|
+
if (attachment.localPath && canUseOriginalLocalAttachment(attachment) && isReadableLocalAttachment(attachment.localPath)) {
|
|
904
|
+
return {
|
|
905
|
+
path: attachment.localPath,
|
|
906
|
+
name: attachment.name || path.basename(attachment.localPath) || 'attachment',
|
|
907
|
+
mimeType: attachment.mimeType || mimeTypeFromExternalAttachment(attachment),
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
if (attachment.url && canUseOriginalUrlAttachment(attachment)) {
|
|
911
|
+
return {
|
|
912
|
+
path: attachment.url,
|
|
913
|
+
name: attachment.name || attachment.url.split('/').filter(Boolean).at(-1) || 'attachment',
|
|
914
|
+
mimeType: attachment.mimeType || mimeTypeFromExternalAttachment(attachment),
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
if (attachment.thumbnailPath && isReadableLocalAttachment(attachment.thumbnailPath)) {
|
|
918
|
+
return {
|
|
919
|
+
path: attachment.thumbnailPath,
|
|
920
|
+
name: attachment.name ? `${attachment.name}-preview.png` : path.basename(attachment.thumbnailPath) || 'preview.png',
|
|
921
|
+
mimeType: 'image/png',
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
function externalAttachmentUnavailableStatus(attachment) {
|
|
927
|
+
if (attachment.providerError)
|
|
928
|
+
return attachment.providerError;
|
|
929
|
+
if (attachment.localPath && (!attachment.availability || attachment.availability === 'edge-local'))
|
|
930
|
+
return 'edge-local-unavailable';
|
|
931
|
+
return attachment.availability || 'metadata-only';
|
|
932
|
+
}
|
|
933
|
+
function canUseOriginalLocalAttachment(attachment) {
|
|
934
|
+
return !attachment.providerError
|
|
935
|
+
&& (!attachment.availability || attachment.availability === 'edge-local');
|
|
936
|
+
}
|
|
937
|
+
function canUseOriginalUrlAttachment(attachment) {
|
|
938
|
+
return !attachment.providerError
|
|
939
|
+
&& (!attachment.availability || attachment.availability === 'server-url');
|
|
940
|
+
}
|
|
941
|
+
function isReadableLocalAttachment(filePath) {
|
|
942
|
+
try {
|
|
943
|
+
return fs.statSync(filePath).isFile();
|
|
944
|
+
}
|
|
945
|
+
catch {
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function mimeTypeFromExternalAttachment(attachment) {
|
|
950
|
+
if (attachment.type === 'image')
|
|
951
|
+
return 'image/*';
|
|
952
|
+
if (attachment.type === 'video')
|
|
953
|
+
return 'video/*';
|
|
954
|
+
if (attachment.type === 'audio')
|
|
955
|
+
return 'audio/*';
|
|
956
|
+
return 'application/octet-stream';
|
|
957
|
+
}
|
|
958
|
+
function parseCloudOcrMode(value) {
|
|
959
|
+
return value === 'off' || value === 'fallback' || value === 'always' ? value : undefined;
|
|
960
|
+
}
|
|
961
|
+
function optionalNumber(value) {
|
|
962
|
+
const number = Number(value);
|
|
963
|
+
return Number.isFinite(number) ? number : undefined;
|
|
964
|
+
}
|
|
@@ -106,6 +106,13 @@ export class NativeSessionFusionService {
|
|
|
106
106
|
for (const filePath of files) {
|
|
107
107
|
const stat = fs.statSync(filePath);
|
|
108
108
|
const current = state.files[filePath];
|
|
109
|
+
if (!current) {
|
|
110
|
+
nextFilesState[filePath] = {
|
|
111
|
+
offset: stat.size,
|
|
112
|
+
mtimeMs: stat.mtimeMs,
|
|
113
|
+
};
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
109
116
|
const isCodex = isCodexRolloutPath(filePath);
|
|
110
117
|
const isOpenCode = filePath.endsWith('.opencode-session.json');
|
|
111
118
|
const isCodexRepairBackfillFile = backfillCodexAppContextWrapper && isCodex && current != null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ZipArchiveOptions = {
|
|
2
|
+
maxFiles?: number;
|
|
3
|
+
maxTotalSize?: number;
|
|
4
|
+
};
|
|
5
|
+
export type ZipArchiveResult = {
|
|
6
|
+
outputPath: string;
|
|
7
|
+
fileCount: number;
|
|
8
|
+
totalSize: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function createZipArchive(sourceDir: string, outputPath: string, options?: ZipArchiveOptions): ZipArchiveResult;
|