shennian 0.2.88 → 0.2.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +22 -0
  2. package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
  3. package/dist/bin/shennian.js +1 -1
  4. package/dist/publish-build-manifest.json +548 -0
  5. package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
  6. package/dist/src/agent-env.js +4 -105
  7. package/dist/src/agents/adapter.d.ts +6 -0
  8. package/dist/src/agents/adapter.js +1 -19
  9. package/dist/src/agents/claude.js +8 -305
  10. package/dist/src/agents/codex-control.d.ts +35 -0
  11. package/dist/src/agents/codex-control.js +2 -0
  12. package/dist/src/agents/codex-utils.js +7 -200
  13. package/dist/src/agents/codex.d.ts +8 -0
  14. package/dist/src/agents/codex.js +15 -863
  15. package/dist/src/agents/command-spec.js +2 -413
  16. package/dist/src/agents/config-status.js +1 -226
  17. package/dist/src/agents/cursor.js +1 -249
  18. package/dist/src/agents/custom.js +4 -271
  19. package/dist/src/agents/detect.js +1 -56
  20. package/dist/src/agents/external-channel-instructions.js +10 -94
  21. package/dist/src/agents/gemini.js +1 -173
  22. package/dist/src/agents/manager.js +13 -157
  23. package/dist/src/agents/model-registry/cache.js +1 -37
  24. package/dist/src/agents/model-registry/discovery.js +2 -187
  25. package/dist/src/agents/model-registry/parsers.js +4 -447
  26. package/dist/src/agents/model-registry/runner.js +1 -30
  27. package/dist/src/agents/model-registry/service.js +1 -78
  28. package/dist/src/agents/model-registry/types.js +1 -8
  29. package/dist/src/agents/model-registry.js +1 -18
  30. package/dist/src/agents/openclaw.js +2 -275
  31. package/dist/src/agents/opencode.js +1 -231
  32. package/dist/src/agents/pi-context.js +12 -217
  33. package/dist/src/agents/pi.js +14 -723
  34. package/dist/src/agents/platform-instructions.js +9 -54
  35. package/dist/src/channels/base.d.ts +4 -1
  36. package/dist/src/channels/base.js +1 -3
  37. package/dist/src/channels/registry.js +1 -30
  38. package/dist/src/channels/reply-split.js +10 -89
  39. package/dist/src/channels/runtime.d.ts +1 -0
  40. package/dist/src/channels/runtime.js +5 -533
  41. package/dist/src/channels/secret-registry.d.ts +1 -0
  42. package/dist/src/channels/secret-registry.js +1 -46
  43. package/dist/src/channels/websocket.js +8 -378
  44. package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
  45. package/dist/src/channels/wechat-channel/anchor.js +1 -0
  46. package/dist/src/channels/wechat-channel/client.d.ts +74 -0
  47. package/dist/src/channels/wechat-channel/client.js +1 -0
  48. package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
  49. package/dist/src/channels/wechat-channel/cooldown.js +1 -0
  50. package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
  51. package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
  52. package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
  53. package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
  54. package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
  55. package/dist/src/channels/wechat-channel/helper-client.js +3 -0
  56. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
  57. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
  58. package/dist/src/channels/wechat-channel/index.d.ts +17 -0
  59. package/dist/src/channels/wechat-channel/index.js +1 -0
  60. package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
  61. package/dist/src/channels/wechat-channel/ledger.js +1 -0
  62. package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
  63. package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
  64. package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
  65. package/dist/src/channels/wechat-channel/message-key.js +1 -0
  66. package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
  67. package/dist/src/channels/wechat-channel/observer.js +1 -0
  68. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
  69. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
  70. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  71. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  72. package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
  73. package/dist/src/channels/wechat-channel/preflight.js +1 -0
  74. package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
  75. package/dist/src/channels/wechat-channel/runner.js +1 -0
  76. package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
  77. package/dist/src/channels/wechat-channel/runtime.js +1 -0
  78. package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
  79. package/dist/src/channels/wechat-channel/scheduler.js +1 -0
  80. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  81. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  82. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  83. package/dist/src/channels/wechat-rpa.d.ts +21 -0
  84. package/dist/src/channels/wechat-rpa.js +6 -1022
  85. package/dist/src/channels/wecom.js +4 -357
  86. package/dist/src/commands/agent.js +6 -131
  87. package/dist/src/commands/daemon-windows.js +8 -48
  88. package/dist/src/commands/daemon.js +19 -1013
  89. package/dist/src/commands/external-attachments.js +1 -51
  90. package/dist/src/commands/external.js +1 -137
  91. package/dist/src/commands/manager.js +2 -389
  92. package/dist/src/commands/pair-qr.js +1 -6
  93. package/dist/src/commands/pair.js +9 -287
  94. package/dist/src/commands/tools.js +1 -34
  95. package/dist/src/commands/upgrade.js +1 -198
  96. package/dist/src/config/index.js +1 -35
  97. package/dist/src/daemon-log.js +6 -58
  98. package/dist/src/env-path.js +1 -64
  99. package/dist/src/fs/boundary.js +1 -126
  100. package/dist/src/fs/handler.js +1 -130
  101. package/dist/src/fs/security.js +1 -32
  102. package/dist/src/fs/text-decoder.d.ts +10 -0
  103. package/dist/src/fs/text-decoder.js +1 -0
  104. package/dist/src/index.js +2 -404
  105. package/dist/src/log-reporter.js +1 -16
  106. package/dist/src/manager/prompt.js +29 -34
  107. package/dist/src/manager/registry.js +2 -269
  108. package/dist/src/manager/runtime.js +19 -1003
  109. package/dist/src/native-fusion/config.js +1 -5
  110. package/dist/src/native-fusion/opencode-parser.js +3 -123
  111. package/dist/src/native-fusion/parser-common.js +8 -264
  112. package/dist/src/native-fusion/parsers.js +8 -729
  113. package/dist/src/native-fusion/service.d.ts +10 -0
  114. package/dist/src/native-fusion/service.js +2 -198
  115. package/dist/src/native-fusion/state.js +1 -22
  116. package/dist/src/native-fusion/types.js +1 -1
  117. package/dist/src/region.js +1 -88
  118. package/dist/src/relay/client.js +1 -343
  119. package/dist/src/session/archive-zip.js +1 -220
  120. package/dist/src/session/handlers/agent-config.js +1 -150
  121. package/dist/src/session/handlers/agents.js +1 -55
  122. package/dist/src/session/handlers/chat.js +2 -733
  123. package/dist/src/session/handlers/control.js +1 -55
  124. package/dist/src/session/handlers/fs.js +1 -747
  125. package/dist/src/session/handlers/session-refresh.js +1 -35
  126. package/dist/src/session/handlers/skills.js +1 -121
  127. package/dist/src/session/handlers/title.js +1 -60
  128. package/dist/src/session/handlers/tool-detail.d.ts +3 -0
  129. package/dist/src/session/handlers/tool-detail.js +1 -0
  130. package/dist/src/session/manager.d.ts +3 -0
  131. package/dist/src/session/manager.js +1 -261
  132. package/dist/src/session/projection.js +1 -54
  133. package/dist/src/session/queue.js +4 -317
  134. package/dist/src/session/remote-attachments.js +1 -72
  135. package/dist/src/session/store.js +3 -109
  136. package/dist/src/session/types.d.ts +4 -0
  137. package/dist/src/session/types.js +1 -4
  138. package/dist/src/skills/registry.js +15 -148
  139. package/dist/src/skills/setup.js +1 -101
  140. package/dist/src/tools/markdown-to-pdf.js +10 -346
  141. package/dist/src/upgrade/engine.js +3 -347
  142. package/package.json +3 -2
  143. package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
@@ -1,1007 +1,23 @@
1
- // @arch docs/features/manager-agent.md
2
- // @test src/__tests__/manager-runtime.test.ts
3
- import http from 'node:http';
4
- import { randomBytes, randomUUID } from 'node:crypto';
5
- import fs from 'node:fs';
6
- import os from 'node:os';
7
- import path from 'node:path';
8
- import { AVAILABLE_BUILTIN_AGENT_TYPES, extractPayloadText, isToolPayload, } from '@shennian/wire';
9
- import { ManagerRegistry } from './registry.js';
10
- import { readMessages } from '../session/store.js';
11
- import { ChannelRuntime } from '../channels/runtime.js';
12
- import { splitExternalReplyText } from '../channels/reply-split.js';
13
- import { resolveShennianPath } from '../config/index.js';
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);
16
- let singleton = null;
17
- export function setManagerRuntimeService(service) {
18
- singleton = service;
19
- }
20
- export function getManagerRuntimeService() {
21
- return singleton;
22
- }
23
- function normalizeWorkDir(workDir) {
24
- return path.resolve(workDir || os.homedir());
25
- }
26
- function json(res, status, body) {
27
- res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
28
- res.end(JSON.stringify(body));
29
- }
30
- function runIdFromMessageId(id) {
31
- const match = /^agent-(.+)-\d+$/.exec(id);
32
- return match?.[1] ?? null;
33
- }
34
- function isManagerWorkerAgentType(agentType) {
35
- if (agentType === 'manager')
36
- return false;
37
- if (agentType.startsWith('custom:'))
38
- return true;
39
- return AVAILABLE_BUILTIN_AGENT_TYPES.includes(agentType);
40
- }
41
- function seqFromMessageId(id) {
42
- const match = /^agent-.+-(\d+)$/.exec(id);
43
- if (!match)
44
- return null;
45
- const seq = Number(match[1]);
46
- return Number.isInteger(seq) && seq >= 0 ? seq : null;
47
- }
48
- function normalizeMarkdownForWorkerSummary(text) {
49
- return text.replace(/\r\n/g, '\n').trim();
50
- }
51
- function toolSummary(payload) {
52
- try {
53
- const parsed = JSON.parse(payload);
54
- const type = parsed.type === 'tool_result' || parsed.result ? 'tool_result' : 'tool_call';
55
- const name = parsed.name || 'tool';
56
- const result = typeof parsed.result === 'string' ? parsed.result.replace(/\s+/g, ' ').trim() : '';
57
- const clipped = result.length > 220 ? `${result.slice(0, 220)}...` : result;
58
- return clipped ? `[${type}] ${name}: ${clipped}` : `[${type}] ${name}`;
59
- }
60
- catch {
61
- return '[tool]';
62
- }
63
- }
64
- function parseExternalReplyAttachment(value) {
65
- if (!value || typeof value !== 'object')
66
- return undefined;
67
- const record = value;
68
- const kind = String(record.kind || '');
69
- const name = String(record.name || '');
70
- const mimeType = String(record.mimeType || '');
71
- const dataBase64 = String(record.dataBase64 || '');
72
- const localPath = String(record.localPath || '');
73
- const url = String(record.url || '');
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');
77
- if (kind !== 'image' && kind !== 'video' && kind !== 'file')
78
- return undefined;
79
- if (!name || !mimeType || !Number.isFinite(size) || size < 0)
80
- return undefined;
81
- if (!localPath && !url)
82
- return undefined;
83
- return {
84
- kind,
85
- name,
86
- mimeType,
87
- size,
88
- ...(localPath ? { localPath } : {}),
89
- ...(url ? { url } : {}),
90
- };
91
- }
92
- function compactWorkerTranscript(rawMessages, limit) {
93
- const chronological = [...rawMessages].sort((a, b) => a.ts - b.ts);
94
- const compacted = [];
95
- let buffer = null;
96
- let bufferRunId = null;
97
- let bufferSeq = null;
98
- let bufferText = '';
99
- const flush = () => {
100
- if (!buffer)
101
- return;
102
- const text = bufferText.trim();
103
- if (text) {
104
- compacted.push({
105
- ...buffer,
106
- id: `${buffer.id}-compact`,
107
- payload: text,
108
- });
109
- }
110
- buffer = null;
111
- bufferRunId = null;
112
- bufferSeq = null;
113
- bufferText = '';
114
- };
115
- for (const message of chronological) {
116
- if (message.role === 'user') {
117
- flush();
118
- compacted.push(message);
119
- continue;
120
- }
121
- if (isToolPayload(message.payload)) {
122
- flush();
123
- compacted.push({
124
- ...message,
125
- payload: toolSummary(message.payload),
126
- });
127
- continue;
128
- }
129
- const text = extractPayloadText(message.payload);
130
- if (!text.trim())
131
- continue;
132
- const runId = runIdFromMessageId(message.id);
133
- const seq = seqFromMessageId(message.id);
134
- if (buffer && buffer.role === message.role && bufferRunId === runId && runId && seq !== null && bufferSeq !== null && seq === bufferSeq + 1) {
135
- bufferText += text;
136
- buffer.ts = message.ts;
137
- bufferSeq = seq;
138
- }
139
- else {
140
- flush();
141
- buffer = message;
142
- bufferRunId = runId;
143
- bufferSeq = seq;
144
- bufferText = text;
145
- }
146
- }
147
- flush();
148
- return compacted.slice(-limit).sort((a, b) => b.ts - a.ts);
149
- }
150
- async function readJson(req) {
151
- const chunks = [];
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
- }
161
- const raw = Buffer.concat(chunks).toString('utf-8');
162
- return raw ? JSON.parse(raw) : {};
163
- }
164
- function appJson(runtime, reqId, ok, payload) {
165
- runtime.client.sendRes({
166
- type: 'res',
167
- id: reqId,
168
- ok,
169
- ...(ok ? { payload } : { error: String(payload.error || 'unknown error') }),
170
- });
171
- }
172
- function shouldFallbackToLocalChannel(error) {
173
- return /binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(error);
174
- }
175
- function managerIpcRuntimePath() {
176
- return resolveShennianPath('runtime', 'manager-ipc.json');
177
- }
178
- export class ManagerRuntimeService {
179
- opts;
180
- registry = new ManagerRegistry();
181
- channelRuntime = new ChannelRuntime((managerSessionId, event) => {
182
- this.handleExternalMessage(managerSessionId, event);
183
- }, (input) => this.registry.createReplyTarget(input).replyTarget);
184
- server = null;
185
- ipcUrl = null;
186
- ipcToken = randomBytes(24).toString('hex');
187
- healthTimer = null;
188
- startPromise = null;
189
- workerTextAcc = new Map();
190
- constructor(opts) {
191
- this.opts = opts;
192
- }
193
- async start() {
194
- if (this.startPromise)
195
- return this.startPromise;
196
- this.startPromise = this.doStart();
197
- return this.startPromise;
198
- }
199
- async doStart() {
200
- if (this.server)
201
- return;
202
- this.server = http.createServer((req, res) => {
203
- void this.handleIpc(req, res);
204
- });
205
- await new Promise((resolve, reject) => {
206
- this.server.once('error', reject);
207
- this.server.listen(0, '127.0.0.1', () => resolve());
208
- });
209
- const address = this.server.address();
210
- if (typeof address === 'object' && address) {
211
- this.ipcUrl = `http://127.0.0.1:${address.port}`;
212
- this.writeIpcRuntimeFile();
213
- }
214
- this.server.unref();
215
- await this.channelRuntime.start();
216
- this.broadcastConfiguredChannelStatuses();
217
- this.healthTimer = setInterval(() => this.scanWorkerHealth(), 60_000);
218
- this.healthTimer.unref();
219
- }
220
- async ready() {
221
- await this.start();
222
- }
223
- async stop() {
224
- if (this.healthTimer)
225
- clearInterval(this.healthTimer);
226
- this.healthTimer = null;
227
- await this.channelRuntime.stop();
228
- await new Promise((resolve) => {
229
- if (!this.server)
230
- return resolve();
231
- this.server.close(() => resolve());
232
- });
233
- this.server = null;
234
- this.ipcUrl = null;
235
- this.removeIpcRuntimeFile();
236
- }
237
- writeIpcRuntimeFile() {
238
- if (!this.ipcUrl)
239
- return;
240
- try {
241
- const filePath = managerIpcRuntimePath();
242
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
243
- fs.writeFileSync(filePath, JSON.stringify({
244
- url: this.ipcUrl,
245
- token: this.ipcToken,
246
- pid: process.pid,
247
- updatedAt: new Date().toISOString(),
248
- }, null, 2), { mode: 0o600 });
249
- fs.chmodSync(filePath, 0o600);
250
- }
251
- catch {
252
- // Best effort. Injected env remains the primary path for managed agents.
253
- }
254
- }
255
- removeIpcRuntimeFile() {
256
- try {
257
- const filePath = managerIpcRuntimePath();
258
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
259
- if (parsed.url === this.ipcUrl || parsed.token === this.ipcToken) {
260
- fs.unlinkSync(filePath);
261
- }
262
- }
263
- catch {
264
- // The file may not exist or may already have been replaced by a newer daemon.
265
- }
266
- }
267
- getInjectedEnv(managerSessionId, agentSessionId, workDir, modelId) {
268
- if (!this.ipcUrl)
269
- return {};
270
- return {
271
- SHENNIAN_MANAGER_SESSION_ID: managerSessionId,
272
- SHENNIAN_MANAGER_AGENT_SESSION_ID: agentSessionId ?? '',
273
- SHENNIAN_MANAGER_WORKDIR: normalizeWorkDir(workDir),
274
- SHENNIAN_MANAGER_MODEL: modelId,
275
- SHENNIAN_MANAGER_IPC_URL: this.ipcUrl,
276
- SHENNIAN_MANAGER_IPC_TOKEN: this.ipcToken,
277
- };
278
- }
279
- registerManager(input) {
280
- this.registry.upsertManager({
281
- ...input,
282
- workDir: normalizeWorkDir(input.workDir),
283
- machineId: process.env.SHENNIAN_MACHINE_ID ?? null,
284
- });
285
- }
286
- setManagerWorkerDefaults(sessionId, agentType, modelId) {
287
- const manager = this.registry.getManager(sessionId);
288
- if (!manager)
289
- return;
290
- this.registry.upsertManager({
291
- ...manager,
292
- defaultWorkerAgentType: agentType ?? null,
293
- defaultWorkerModelId: modelId ?? null,
294
- });
295
- }
296
- getManagerWorkerDefaults(sessionId) {
297
- const manager = this.registry.getManager(sessionId);
298
- return {
299
- agentType: manager?.defaultWorkerAgentType ?? null,
300
- modelId: manager?.defaultWorkerModelId ?? null,
301
- };
302
- }
303
- noteManagerAgentSession(sessionId, agentSessionId, workDir, modelId) {
304
- this.registry.upsertManager({
305
- sessionId,
306
- agentSessionId,
307
- workDir: normalizeWorkDir(workDir),
308
- machineId: process.env.SHENNIAN_MACHINE_ID ?? null,
309
- modelId,
310
- });
311
- }
312
- noteAgentEvent(sessionId, event) {
313
- const worker = this.findWorker(sessionId);
314
- if (!worker)
315
- return;
316
- const patch = {
317
- lastActivityAt: new Date().toISOString(),
318
- runId: event.runId,
319
- };
320
- if (event.agentSessionId)
321
- patch.agentSessionId = event.agentSessionId;
322
- const textKey = `${sessionId}:${event.runId}`;
323
- if (event.state === 'delta' && event.text && !event.thinking) {
324
- const nextText = (this.workerTextAcc.get(textKey) ?? '') + event.text;
325
- this.workerTextAcc.set(textKey, nextText);
326
- const normalized = normalizeMarkdownForWorkerSummary(nextText);
327
- if (normalized) {
328
- patch.summary = normalized.length > 160 ? `${normalized.slice(0, 160)}...` : normalized;
329
- }
330
- }
331
- if (event.state === 'final' || event.state === 'error' || event.state === 'aborted') {
332
- patch.status = event.state;
333
- const accumulated = normalizeMarkdownForWorkerSummary(this.workerTextAcc.get(textKey) ?? '');
334
- if (accumulated) {
335
- patch.summary = accumulated.length > 240 ? `${accumulated.slice(0, 240)}...` : accumulated;
336
- }
337
- this.workerTextAcc.delete(textKey);
338
- }
339
- else if (event.state === 'start') {
340
- patch.status = 'running';
341
- }
342
- const updated = this.registry.updateWorker(sessionId, patch);
343
- if (updated && (event.state === 'final' || event.state === 'error' || event.state === 'aborted')) {
344
- this.wakeManagerForWorker(updated.managedBy, updated, event.state, event.message);
345
- }
346
- }
347
- findWorker(sessionId) {
348
- const registry = this.registry.load();
349
- return registry.workers[sessionId];
350
- }
351
- async handleAppReq(req) {
352
- const runtime = this.opts.getRuntime();
353
- const body = (req.params ?? {});
354
- try {
355
- const managerSessionId = String(body.managerSessionId || body.sessionId || '');
356
- if (!managerSessionId)
357
- throw new Error('sessionId is required');
358
- const manager = this.registry.getManager(managerSessionId);
359
- if (req.method === 'manager.channel.get') {
360
- appJson(runtime, req.id, true, {
361
- channel: this.channelRuntime.getManagerChannel(managerSessionId, 'websocket', { includeSecret: true }),
362
- });
363
- return;
364
- }
365
- if (req.method === 'manager.channel.upsert') {
366
- const channel = await this.channelRuntime.upsertManagerChannel({
367
- id: String(body.id || `websocket:${managerSessionId}`),
368
- managerSessionId,
369
- sessionId: managerSessionId,
370
- workDir: String(body.workDir || manager?.workDir || ''),
371
- type: 'websocket',
372
- name: typeof body.name === 'string' ? body.name : undefined,
373
- agentType: typeof body.agentType === 'string' ? body.agentType : undefined,
374
- agentSessionId: typeof body.agentSessionId === 'string' ? body.agentSessionId : null,
375
- modelId: typeof body.modelId === 'string' ? body.modelId : null,
376
- enabled: Boolean(body.enabled),
377
- wsUrl: typeof body.wsUrl === 'string' ? body.wsUrl : undefined,
378
- token: typeof body.token === 'string' ? body.token : undefined,
379
- canReply: body.canReply === undefined ? undefined : Boolean(body.canReply),
380
- systemPrompt: typeof body.systemPrompt === 'string' ? body.systemPrompt : undefined,
381
- });
382
- this.broadcastManagerChannelStatus(managerSessionId);
383
- appJson(runtime, req.id, true, { channel });
384
- return;
385
- }
386
- throw new Error(`Unsupported manager app method: ${req.method}`);
387
- }
388
- catch (error) {
389
- appJson(runtime, req.id, false, { error: error instanceof Error ? error.message : String(error) });
390
- }
391
- }
392
- broadcastConfiguredChannelStatuses() {
393
- for (const entry of this.channelRuntime.listManagerChannelStatuses()) {
394
- this.broadcastManagerChannelStatus(entry.managerSessionId);
395
- }
396
- }
397
- broadcastManagerChannelStatus(managerSessionId) {
398
- const runtime = this.opts.getRuntime();
399
- if (!runtime.client?.sendEvent)
400
- return;
401
- const manager = this.registry.getManager(managerSessionId);
402
- const status = this.channelRuntime.getManagerChannelStatus(managerSessionId);
403
- runtime.client.sendEvent({
404
- type: 'event',
405
- event: 'session.update',
406
- payload: {
407
- session: {
408
- id: managerSessionId,
409
- agentType: manager ? 'manager' : undefined,
410
- agentSessionId: manager?.agentSessionId ?? null,
411
- modelId: manager?.modelId ?? null,
412
- workDir: manager?.workDir,
413
- externalChannel: status,
414
- },
415
- },
416
- });
417
- }
418
- async handleIpc(req, res) {
419
- if (req.headers.authorization !== `Bearer ${this.ipcToken}`) {
420
- json(res, 401, { ok: false, error: 'Unauthorized' });
421
- return;
422
- }
423
- try {
424
- const url = new URL(req.url ?? '/', 'http://127.0.0.1');
425
- const body = await readJson(req);
426
- const managerSessionId = String(body.managerSessionId || req.headers['x-shennian-manager-session-id'] || '');
427
- if (!managerSessionId)
428
- throw new Error('managerSessionId is required');
429
- const manager = this.registry.getManager(managerSessionId);
430
- if (url.pathname === '/sessions/list') {
431
- if (!manager)
432
- throw new Error('Manager runtime is not registered');
433
- const runningSessionIds = new Set(this.opts.getRuntime().sessions.keys());
434
- json(res, 200, {
435
- ok: true,
436
- sessions: this.registry.listWorkers(managerSessionId, { runningSessionIds }),
437
- });
438
- return;
439
- }
440
- if (url.pathname === '/sessions/start') {
441
- if (!manager)
442
- throw new Error('Manager runtime is not registered');
443
- const agentType = String(body.agentType || body.agent || manager.defaultWorkerAgentType || 'codex');
444
- if (!isManagerWorkerAgentType(agentType)) {
445
- throw new Error(`Unsupported manager worker agent: ${agentType}`);
446
- }
447
- const workDir = normalizeWorkDir(String(body.workDir || manager.workDir));
448
- if (workDir !== manager.workDir)
449
- throw new Error('Manager can only start workers in the same workDir');
450
- const message = String(body.message || '');
451
- if (!message)
452
- throw new Error('message is required');
453
- const worker = this.registry.addWorker({
454
- managerSessionId,
455
- agentType,
456
- workDir,
457
- summary: message.slice(0, 120),
458
- });
459
- const workerModelId = String(body.modelId || (agentType === manager.defaultWorkerAgentType ? manager.defaultWorkerModelId ?? '' : ''));
460
- await this.dispatchChatSend(worker.sessionId, agentType, workDir, message, null, workerModelId);
461
- json(res, 200, { ok: true, session: worker });
462
- return;
463
- }
464
- if (url.pathname === '/sessions/send') {
465
- const sessionId = String(body.sessionId || '');
466
- const message = String(body.message || '');
467
- const enqueue = body.enqueue === undefined ? true : Boolean(body.enqueue);
468
- const worker = this.registry.getWorkerForManager(managerSessionId, sessionId);
469
- if (!worker)
470
- throw new Error('Worker not found in this manager scope');
471
- const workerModelId = String(body.modelId || (worker.agentType === manager?.defaultWorkerAgentType ? manager.defaultWorkerModelId ?? '' : ''));
472
- if (enqueue) {
473
- await this.dispatchChatEnqueue(worker.sessionId, worker.agentType, worker.workDir, message, worker.agentSessionId ?? null, workerModelId);
474
- }
475
- else {
476
- await this.dispatchChatSend(worker.sessionId, worker.agentType, worker.workDir, message, worker.agentSessionId ?? null, workerModelId);
477
- }
478
- json(res, 200, { ok: true });
479
- return;
480
- }
481
- if (url.pathname === '/sessions/queue') {
482
- const sessionId = String(body.sessionId || '');
483
- const worker = this.registry.getWorkerForManager(managerSessionId, sessionId);
484
- if (!worker)
485
- throw new Error('Worker not found in this manager scope');
486
- const queue = this.opts.getRuntime().chatQueue?.getSnapshot(sessionId);
487
- json(res, 200, { ok: true, queue });
488
- return;
489
- }
490
- if (url.pathname === '/sessions/queue/edit') {
491
- const sessionId = String(body.sessionId || '');
492
- const worker = this.registry.getWorkerForManager(managerSessionId, sessionId);
493
- if (!worker)
494
- throw new Error('Worker not found in this manager scope');
495
- await this.opts.dispatchReq({
496
- type: 'req',
497
- id: `manager-queue-edit-${randomUUID()}`,
498
- method: 'chat.queue.edit',
499
- params: {
500
- sessionId,
501
- queueMessageId: String(body.queueMessageId || body.messageId || ''),
502
- text: String(body.message || body.text || ''),
503
- },
504
- });
505
- json(res, 200, { ok: true, queue: this.opts.getRuntime().chatQueue?.getSnapshot(sessionId) });
506
- return;
507
- }
508
- if (url.pathname === '/sessions/queue/delete') {
509
- const sessionId = String(body.sessionId || '');
510
- const worker = this.registry.getWorkerForManager(managerSessionId, sessionId);
511
- if (!worker)
512
- throw new Error('Worker not found in this manager scope');
513
- await this.opts.dispatchReq({
514
- type: 'req',
515
- id: `manager-queue-delete-${randomUUID()}`,
516
- method: 'chat.queue.delete',
517
- params: {
518
- sessionId,
519
- queueMessageId: String(body.queueMessageId || body.messageId || ''),
520
- },
521
- });
522
- json(res, 200, { ok: true, queue: this.opts.getRuntime().chatQueue?.getSnapshot(sessionId) });
523
- return;
524
- }
525
- if (url.pathname === '/sessions/stop' || url.pathname === '/sessions/terminate') {
526
- const sessionId = String(body.sessionId || '');
527
- const worker = this.registry.getWorkerForManager(managerSessionId, sessionId);
528
- if (!worker)
529
- throw new Error('Worker not found in this manager scope');
530
- await this.opts.dispatchReq({ type: 'req', id: `manager-abort-${randomUUID()}`, method: 'chat.abort', params: { sessionId } });
531
- this.registry.updateWorker(sessionId, { status: 'aborted' });
532
- json(res, 200, { ok: true });
533
- return;
534
- }
535
- if (url.pathname === '/sessions/read') {
536
- const sessionId = String(body.sessionId || '');
537
- const limit = Number(body.limit || 200);
538
- const worker = this.registry.getWorkerForManager(managerSessionId, sessionId);
539
- if (!worker)
540
- throw new Error('Worker not found in this manager scope');
541
- const rawMessages = readMessages(sessionId, { limit: Math.max(limit * 20, limit) });
542
- json(res, 200, {
543
- ok: true,
544
- messages: compactWorkerTranscript(rawMessages, limit),
545
- rawMessageCount: rawMessages.length,
546
- });
547
- return;
548
- }
549
- if (url.pathname === '/memory/path') {
550
- if (!manager)
551
- throw new Error('Manager runtime is not registered');
552
- json(res, 200, { ok: true, path: path.join(manager.workDir, '.shennian') });
553
- return;
554
- }
555
- if (url.pathname === '/external/reply') {
556
- const replyTarget = typeof body.replyTarget === 'string'
557
- ? this.registry.getReplyTarget(body.replyTarget)
558
- : this.registry.getLatestReplyTargetForManager(managerSessionId);
559
- const text = String(body.text || '');
560
- const attachment = parseExternalReplyAttachment(body.attachment);
561
- const idempotencyKey = String(body.idempotencyKey || randomUUID());
562
- const explicitChannelId = String(body.channelId || '');
563
- const explicitConversationId = String(body.conversationId || '');
564
- const defaultTarget = !replyTarget && (!explicitChannelId || !explicitConversationId)
565
- ? await this.channelRuntime.getDefaultReplyTarget(managerSessionId).catch(() => null)
566
- : null;
567
- const channelId = replyTarget?.channelId || explicitChannelId || defaultTarget?.channelId || '';
568
- const conversationId = replyTarget?.conversationId || explicitConversationId || defaultTarget?.conversationId || '';
569
- if (channelId && this.channelRuntime.getChannelById(channelId)) {
570
- if (!conversationId)
571
- throw new Error('No external channel target is available for this Manager');
572
- const result = await this.channelRuntime.reply({
573
- managerSessionId,
574
- channelId,
575
- conversationId,
576
- messageId: replyTarget?.messageId ?? undefined,
577
- text,
578
- attachment,
579
- idempotencyKey,
580
- });
581
- json(res, result.ok ? 200 : 400, result);
582
- return;
583
- }
584
- let relayResult;
585
- try {
586
- relayResult = await this.sendManagedWeComReply({
587
- managerSessionId,
588
- text,
589
- attachment,
590
- idempotencyKey,
591
- });
592
- }
593
- catch (error) {
594
- const message = error instanceof Error ? error.message : String(error);
595
- if (!shouldFallbackToLocalChannel(message))
596
- throw error;
597
- relayResult = { ok: false, error: message };
598
- }
599
- if (relayResult.ok) {
600
- json(res, 200, { ok: true, payload: relayResult.payload });
601
- return;
602
- }
603
- if (!shouldFallbackToLocalChannel(relayResult.error || '') || !channelId || !conversationId) {
604
- json(res, 400, { ok: false, error: relayResult.error || 'External send failed' });
605
- return;
606
- }
607
- json(res, 400, { ok: false, error: `No local external channel is configured for ${channelId}` });
608
- return;
609
- }
610
- if (url.pathname === '/channel/get') {
611
- json(res, 200, {
612
- ok: true,
613
- channel: this.channelRuntime.getManagerChannel(managerSessionId, 'websocket'),
614
- });
615
- return;
616
- }
617
- if (url.pathname === '/channel/upsert') {
618
- if (!manager)
619
- throw new Error('Manager runtime is not registered');
620
- const channel = await this.channelRuntime.upsertManagerChannel({
621
- id: String(body.id || `websocket:${managerSessionId}`),
622
- managerSessionId,
623
- workDir: manager.workDir,
624
- type: 'websocket',
625
- name: typeof body.name === 'string' ? body.name : undefined,
626
- enabled: Boolean(body.enabled),
627
- wsUrl: typeof body.wsUrl === 'string' ? body.wsUrl : undefined,
628
- token: typeof body.token === 'string' ? body.token : undefined,
629
- canReply: body.canReply === undefined ? undefined : Boolean(body.canReply),
630
- systemPrompt: typeof body.systemPrompt === 'string' ? body.systemPrompt : undefined,
631
- });
632
- this.registry.upsertManager({
633
- ...manager,
634
- status: manager.status,
635
- });
636
- this.broadcastManagerChannelStatus(managerSessionId);
637
- json(res, 200, { ok: true, channel });
638
- return;
639
- }
640
- if (url.pathname === '/wechat-rpa/channel/get') {
641
- json(res, 200, {
642
- ok: true,
643
- channel: this.channelRuntime.getManagerChannel(managerSessionId, 'wechat-rpa', { includeSecret: true }),
644
- });
645
- return;
646
- }
647
- if (url.pathname === '/wechat-rpa/channel/upsert') {
648
- if (!manager)
649
- throw new Error('Manager runtime is not registered');
650
- const channel = await this.channelRuntime.upsertManagerWeChatRpaChannel({
651
- id: String(body.id || `wechat-rpa:${managerSessionId}`),
652
- managerSessionId,
653
- workDir: manager.workDir,
654
- name: typeof body.name === 'string' ? body.name : undefined,
655
- enabled: Boolean(body.enabled),
656
- groups: parseWeChatRpaGroups(body.groups),
657
- canReply: body.canReply === undefined ? undefined : Boolean(body.canReply),
658
- systemPrompt: typeof body.systemPrompt === 'string' ? body.systemPrompt : undefined,
659
- source: parseWeChatRpaSource(body.source),
660
- pollIntervalMs: optionalNumber(body.pollIntervalMs),
661
- recentLimit: optionalNumber(body.recentLimit),
662
- idleSeconds: optionalNumber(body.idleSeconds),
663
- forceForeground: body.forceForeground === undefined ? undefined : Boolean(body.forceForeground),
664
- noRestore: body.noRestore === undefined ? undefined : Boolean(body.noRestore),
665
- downloadAttachments: body.downloadAttachments === undefined ? undefined : Boolean(body.downloadAttachments),
666
- downloadAttachmentsDir: typeof body.downloadAttachmentsDir === 'string' ? body.downloadAttachmentsDir : undefined,
667
- flowScriptPath: typeof body.flowScriptPath === 'string' ? body.flowScriptPath : undefined,
668
- });
669
- this.registry.upsertManager({
670
- ...manager,
671
- status: manager.status,
672
- });
673
- this.broadcastManagerChannelStatus(managerSessionId);
674
- json(res, 200, { ok: true, channel });
675
- return;
676
- }
677
- if (url.pathname === '/wechat-rpa/channel/sync') {
678
- const { channel, messages } = await this.channelRuntime.syncManagerWeChatRpaChannel(managerSessionId);
679
- this.broadcastManagerChannelStatus(managerSessionId);
680
- json(res, 200, { ok: true, channel, messages });
681
- return;
682
- }
683
- json(res, 404, { ok: false, error: `Unknown manager IPC path: ${url.pathname}` });
684
- }
685
- catch (err) {
686
- json(res, 400, { ok: false, error: err instanceof Error ? err.message : String(err) });
687
- }
688
- }
689
- async dispatchChatSend(sessionId, agentType, workDir, text, agentSessionId, modelId) {
690
- await this.opts.dispatchReq({
691
- type: 'req',
692
- id: `manager-send-${randomUUID()}`,
693
- method: 'chat.send',
694
- params: { sessionId, text, agentType, workDir, agentSessionId, modelId },
695
- });
696
- }
697
- async dispatchChatEnqueue(sessionId, agentType, workDir, text, agentSessionId, modelId) {
698
- await this.opts.dispatchReq({
699
- type: 'req',
700
- id: `manager-enqueue-${randomUUID()}`,
701
- method: 'chat.enqueue',
702
- params: { sessionId, text, agentType, workDir, agentSessionId, modelId },
703
- });
704
- }
705
- async sendManagedWeComReply(input) {
706
- const parts = splitExternalReplyText(input.text);
707
- if (!parts.length && !input.attachment)
708
- return { ok: false, error: 'text or attachment is required' };
709
- const client = this.opts.getRuntime().client;
710
- if (!client || typeof client.sendReq !== 'function') {
711
- return { ok: false, error: 'Relay is not connected' };
712
- }
713
- const payloads = [];
714
- for (const [index, text] of parts.entries()) {
715
- const frame = await client.sendReq({
716
- type: 'req',
717
- id: `external-send-${randomUUID()}`,
718
- method: 'external.send',
719
- params: {
720
- managerSessionId: input.managerSessionId,
721
- text,
722
- idempotencyKey: parts.length > 1 ? `${input.idempotencyKey}:${index + 1}` : input.idempotencyKey,
723
- },
724
- });
725
- if (!frame.ok)
726
- return { ok: false, error: frame.error || 'External send failed' };
727
- payloads.push(frame.payload);
728
- }
729
- if (input.attachment) {
730
- const frame = await client.sendReq({
731
- type: 'req',
732
- id: `external-send-${randomUUID()}`,
733
- method: 'external.send',
734
- params: {
735
- managerSessionId: input.managerSessionId,
736
- attachment: input.attachment,
737
- idempotencyKey: parts.length ? `${input.idempotencyKey}:attachment` : input.idempotencyKey,
738
- },
739
- });
740
- if (!frame.ok)
741
- return { ok: false, error: frame.error || 'External send failed' };
742
- payloads.push(frame.payload);
743
- }
744
- return { ok: true, payload: payloads.length === 1 ? payloads[0] : payloads };
745
- }
746
- wakeManagerForWorker(managerSessionId, worker, state, message) {
747
- const manager = this.registry.getManager(managerSessionId);
748
- if (!manager)
749
- return;
750
- const prompt = `你管理的 worker 已结束。
1
+ import W from"node:http";import{randomBytes as q,randomUUID as g}from"node:crypto";import k from"node:fs";import v from"node:os";import I from"node:path";import{AVAILABLE_BUILTIN_AGENT_TYPES as _,extractPayloadText as B,isToolPayload as F}from"@shennian/wire";import{ManagerRegistry as U}from"./registry.js";import{readMessages as L}from"../session/store.js";import{ChannelRuntime as O}from"../channels/runtime.js";import{splitExternalReplyText as H}from"../channels/reply-split.js";import{resolveShennianPath as G}from"../config/index.js";import{buildExternalChannelInstructions as j}from"../agents/external-channel-instructions.js";const M=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024);let x=null;function ke(a){x=a}function Ie(){return x}function R(a){return I.resolve(a||v.homedir())}function u(a,e,r){a.writeHead(e,{"content-type":"application/json; charset=utf-8"}),a.end(JSON.stringify(r))}function K(a){return/^agent-(.+)-\d+$/.exec(a)?.[1]??null}function z(a){return a==="manager"?!1:a.startsWith("custom:")?!0:_.includes(a)}function J(a){const e=/^agent-.+-(\d+)$/.exec(a);if(!e)return null;const r=Number(e[1]);return Number.isInteger(r)&&r>=0?r:null}function T(a){return a.replace(/\r\n/g,`
2
+ `).trim()}function Y(a){try{const e=JSON.parse(a),r=e.type==="tool_result"||e.result?"tool_result":"tool_call",n=e.name||"tool",t=typeof e.result=="string"?e.result.replace(/\s+/g," ").trim():"",s=t.length>220?`${t.slice(0,220)}...`:t;return s?`[${r}] ${n}: ${s}`:`[${r}] ${n}`}catch{return"[tool]"}}function Q(a){if(!a||typeof a!="object")return;const e=a,r=String(e.kind||""),n=String(e.name||""),t=String(e.mimeType||""),s=String(e.dataBase64||""),o=String(e.localPath||""),i=String(e.url||""),l=Number(e.size||0);if(s)throw new Error("Manager IPC external attachments must use localPath or url; dataBase64 is not accepted");if(!(r!=="image"&&r!=="video"&&r!=="file")&&!(!n||!t||!Number.isFinite(l)||l<0)&&!(!o&&!i))return{kind:r,name:n,mimeType:t,size:l,...o?{localPath:o}:{},...i?{url:i}:{}}}function X(a,e){const r=[...a].sort((c,d)=>c.ts-d.ts),n=[];let t=null,s=null,o=null,i="";const l=()=>{if(!t)return;const c=i.trim();c&&n.push({...t,id:`${t.id}-compact`,payload:c}),t=null,s=null,o=null,i=""};for(const c of r){if(c.role==="user"){l(),n.push(c);continue}if(F(c.payload)){l(),n.push({...c,payload:Y(c.payload)});continue}const d=B(c.payload);if(!d.trim())continue;const h=K(c.id),p=J(c.id);t&&t.role===c.role&&s===h&&h&&p!==null&&o!==null&&p===o+1?(i+=d,t.ts=c.ts,o=p):(l(),t=c,s=h,o=p,i=d)}return l(),n.slice(-e).sort((c,d)=>d.ts-c.ts)}async function V(a){const e=[];let r=0;for await(const t of a){const s=Buffer.from(t);if(r+=s.byteLength,Number.isFinite(M)&&M>0&&r>M)throw new Error(`Manager IPC request body is too large. Max: ${M} bytes.`);e.push(s)}const n=Buffer.concat(e).toString("utf-8");return n?JSON.parse(n):{}}function A(a,e,r,n){a.client.sendRes({type:"res",id:e,ok:r,...r?{payload:n}:{error:String(n.error||"unknown error")}})}function C(a){return/binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(a)}function N(){return G("runtime","manager-ipc.json")}class Se{opts;registry=new U;channelRuntime=new O((e,r)=>{this.handleExternalMessage(e,r)},e=>this.registry.createReplyTarget(e).replyTarget);server=null;ipcUrl=null;ipcToken=q(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;constructor(e){this.opts=e}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=W.createServer((r,n)=>{this.handleIpc(r,n)}),await new Promise((r,n)=>{this.server.once("error",n),this.server.listen(0,"127.0.0.1",()=>r())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.broadcastConfiguredChannelStatuses(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=N();k.mkdirSync(I.dirname(e),{recursive:!0}),k.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),k.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=N(),r=JSON.parse(k.readFileSync(e,"utf-8"));(r.url===this.ipcUrl||r.token===this.ipcToken)&&k.unlinkSync(e)}catch{}}getInjectedEnv(e,r,n,t){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:r??"",SHENNIAN_MANAGER_WORKDIR:R(n),SHENNIAN_MANAGER_MODEL:t,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:R(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null})}setManagerWorkerDefaults(e,r,n){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,defaultWorkerAgentType:r??null,defaultWorkerModelId:n??null})}getManagerWorkerDefaults(e){const r=this.registry.getManager(e);return{agentType:r?.defaultWorkerAgentType??null,modelId:r?.defaultWorkerModelId??null}}noteManagerAgentSession(e,r,n,t){this.registry.upsertManager({sessionId:e,agentSessionId:r,workDir:R(n),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:t})}noteAgentEvent(e,r){if(!this.findWorker(e))return;const t={lastActivityAt:new Date().toISOString(),runId:r.runId};r.agentSessionId&&(t.agentSessionId=r.agentSessionId);const s=`${e}:${r.runId}`;if(r.state==="delta"&&r.text&&!r.thinking){const i=(this.workerTextAcc.get(s)??"")+r.text;this.workerTextAcc.set(s,i);const l=T(i);l&&(t.summary=l.length>160?`${l.slice(0,160)}...`:l)}if(r.state==="final"||r.state==="error"||r.state==="aborted"){t.status=r.state;const i=T(this.workerTextAcc.get(s)??"");i&&(t.summary=i.length>240?`${i.slice(0,240)}...`:i),this.workerTextAcc.delete(s)}else r.state==="start"&&(t.status="running");const o=this.registry.updateWorker(e,t);o&&(r.state==="final"||r.state==="error"||r.state==="aborted")&&this.wakeManagerForWorker(o.managedBy,o,r.state,r.message)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){const r=this.opts.getRuntime(),n=e.params??{};try{const t=String(n.managerSessionId||n.sessionId||"");if(!t)throw new Error("sessionId is required");const s=this.registry.getManager(t);if(e.method==="manager.channel.get"){A(r,e.id,!0,{channel:this.channelRuntime.getManagerChannel(t,"websocket",{includeSecret:!0})});return}if(e.method==="manager.channel.upsert"){const o=await this.channelRuntime.upsertManagerChannel({id:String(n.id||`websocket:${t}`),managerSessionId:t,sessionId:t,workDir:String(n.workDir||s?.workDir||""),type:"websocket",name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,wsUrl:typeof n.wsUrl=="string"?n.wsUrl:void 0,token:typeof n.token=="string"?n.token:void 0,canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0});this.broadcastManagerChannelStatus(t),A(r,e.id,!0,{channel:o});return}throw new Error(`Unsupported manager app method: ${e.method}`)}catch(t){A(r,e.id,!1,{error:t instanceof Error?t.message:String(t)})}}broadcastConfiguredChannelStatuses(){for(const e of this.channelRuntime.listManagerChannelStatuses())this.broadcastManagerChannelStatus(e.managerSessionId)}broadcastManagerChannelStatus(e){const r=this.opts.getRuntime();if(!r.client?.sendEvent)return;const n=this.registry.getManager(e),t=this.channelRuntime.getManagerChannelStatus(e);r.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:n?"manager":void 0,agentSessionId:n?.agentSessionId??null,modelId:n?.modelId??null,workDir:n?.workDir,externalChannel:t}}})}async handleIpc(e,r){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){u(r,401,{ok:!1,error:"Unauthorized"});return}try{const n=new URL(e.url??"/","http://127.0.0.1"),t=await V(e),s=String(t.managerSessionId||e.headers["x-shennian-manager-session-id"]||"");if(!s)throw new Error("managerSessionId is required");const o=this.registry.getManager(s);if(n.pathname==="/sessions/list"){if(!o)throw new Error("Manager runtime is not registered");const i=new Set(this.opts.getRuntime().sessions.keys());u(r,200,{ok:!0,sessions:this.registry.listWorkers(s,{runningSessionIds:i})});return}if(n.pathname==="/sessions/start"){if(!o)throw new Error("Manager runtime is not registered");const i=String(t.agentType||t.agent||o.defaultWorkerAgentType||"codex");if(!z(i))throw new Error(`Unsupported manager worker agent: ${i}`);const l=R(String(t.workDir||o.workDir));if(l!==o.workDir)throw new Error("Manager can only start workers in the same workDir");const c=String(t.message||"");if(!c)throw new Error("message is required");const d=this.registry.addWorker({managerSessionId:s,agentType:i,workDir:l,summary:c.slice(0,120)}),h=String(t.modelId||(i===o.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));await this.dispatchChatSend(d.sessionId,i,l,c,null,h),u(r,200,{ok:!0,session:d});return}if(n.pathname==="/sessions/send"){const i=String(t.sessionId||""),l=String(t.message||""),c=t.enqueue===void 0?!0:!!t.enqueue,d=this.registry.getWorkerForManager(s,i);if(!d)throw new Error("Worker not found in this manager scope");const h=String(t.modelId||(d.agentType===o?.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));c?await this.dispatchChatEnqueue(d.sessionId,d.agentType,d.workDir,l,d.agentSessionId??null,h):await this.dispatchChatSend(d.sessionId,d.agentType,d.workDir,l,d.agentSessionId??null,h),u(r,200,{ok:!0});return}if(n.pathname==="/sessions/queue"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const c=this.opts.getRuntime().chatQueue?.getSnapshot(i);u(r,200,{ok:!0,queue:c});return}if(n.pathname==="/sessions/queue/edit"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-edit-${g()}`,method:"chat.queue.edit",params:{sessionId:i,queueMessageId:String(t.queueMessageId||t.messageId||""),text:String(t.message||t.text||"")}}),u(r,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(n.pathname==="/sessions/queue/delete"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-delete-${g()}`,method:"chat.queue.delete",params:{sessionId:i,queueMessageId:String(t.queueMessageId||t.messageId||"")}}),u(r,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(n.pathname==="/sessions/stop"||n.pathname==="/sessions/terminate"){const i=String(t.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-abort-${g()}`,method:"chat.abort",params:{sessionId:i}}),this.registry.updateWorker(i,{status:"aborted"}),u(r,200,{ok:!0});return}if(n.pathname==="/sessions/read"){const i=String(t.sessionId||""),l=Number(t.limit||200);if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const d=L(i,{limit:Math.max(l*20,l)});u(r,200,{ok:!0,messages:X(d,l),rawMessageCount:d.length});return}if(n.pathname==="/memory/path"){if(!o)throw new Error("Manager runtime is not registered");u(r,200,{ok:!0,path:I.join(o.workDir,".shennian")});return}if(n.pathname==="/external/reply"){const i=typeof t.replyTarget=="string"?this.registry.getReplyTarget(t.replyTarget):this.registry.getLatestReplyTargetForManager(s),l=String(t.text||""),c=Q(t.attachment),d=String(t.idempotencyKey||g()),h=String(t.channelId||""),p=String(t.conversationId||""),S=!i&&(!h||!p)?await this.channelRuntime.getDefaultReplyTarget(s).catch(()=>null):null,y=i?.channelId||h||S?.channelId||"",w=i?.conversationId||p||S?.conversationId||"";if(y&&this.channelRuntime.getChannelById(y)){if(!w)throw new Error("No external channel target is available for this Manager");const f=await this.channelRuntime.reply({managerSessionId:s,channelId:y,conversationId:w,messageId:i?.messageId??void 0,text:l,attachment:c,idempotencyKey:d});u(r,f.ok?200:400,f);return}let m;try{m=await this.sendManagedWeComReply({managerSessionId:s,text:l,attachment:c,idempotencyKey:d})}catch(f){const E=f instanceof Error?f.message:String(f);if(!C(E))throw f;m={ok:!1,error:E}}if(m.ok){u(r,200,{ok:!0,payload:m.payload});return}if(!C(m.error||"")||!y||!w){u(r,400,{ok:!1,error:m.error||"External send failed"});return}u(r,400,{ok:!1,error:`No local external channel is configured for ${y}`});return}if(n.pathname==="/channel/get"){u(r,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"websocket")});return}if(n.pathname==="/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const i=await this.channelRuntime.upsertManagerChannel({id:String(t.id||`websocket:${s}`),managerSessionId:s,workDir:o.workDir,type:"websocket",name:typeof t.name=="string"?t.name:void 0,enabled:!!t.enabled,wsUrl:typeof t.wsUrl=="string"?t.wsUrl:void 0,token:typeof t.token=="string"?t.token:void 0,canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(r,200,{ok:!0,channel:i});return}if(n.pathname==="/wechat-rpa/channel/get"){u(r,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"wechat-rpa",{includeSecret:!0})});return}if(n.pathname==="/wechat-rpa/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const i=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(t.id||`wechat-rpa:${s}`),managerSessionId:s,workDir:o.workDir,name:typeof t.name=="string"?t.name:void 0,enabled:!!t.enabled,groups:Z(t.groups),canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0,source:ee(t.source),pollIntervalMs:b(t.pollIntervalMs),recentLimit:b(t.recentLimit),idleSeconds:b(t.idleSeconds),forceForeground:t.forceForeground===void 0?void 0:!!t.forceForeground,noRestore:t.noRestore===void 0?void 0:!!t.noRestore,downloadAttachments:t.downloadAttachments===void 0?void 0:!!t.downloadAttachments,downloadAttachmentsDir:typeof t.downloadAttachmentsDir=="string"?t.downloadAttachmentsDir:void 0,privacyConsentAccepted:t.privacyConsentAccepted===void 0?void 0:!!t.privacyConsentAccepted,flowScriptPath:typeof t.flowScriptPath=="string"?t.flowScriptPath:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(r,200,{ok:!0,channel:i});return}if(n.pathname==="/wechat-rpa/channel/sync"){const{channel:i,messages:l}=await this.channelRuntime.syncManagerWeChatRpaChannel(s);this.broadcastManagerChannelStatus(s),u(r,200,{ok:!0,channel:i,messages:l});return}u(r,404,{ok:!1,error:`Unknown manager IPC path: ${n.pathname}`})}catch(n){u(r,400,{ok:!1,error:n instanceof Error?n.message:String(n)})}}async dispatchChatSend(e,r,n,t,s,o){await this.opts.dispatchReq({type:"req",id:`manager-send-${g()}`,method:"chat.send",params:{sessionId:e,text:t,agentType:r,workDir:n,agentSessionId:s,modelId:o}})}async dispatchChatEnqueue(e,r,n,t,s,o){await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${g()}`,method:"chat.enqueue",params:{sessionId:e,text:t,agentType:r,workDir:n,agentSessionId:s,modelId:o}})}async sendManagedWeComReply(e){const r=H(e.text);if(!r.length&&!e.attachment)return{ok:!1,error:"text or attachment is required"};const n=this.opts.getRuntime().client;if(!n||typeof n.sendReq!="function")return{ok:!1,error:"Relay is not connected"};const t=[];for(const[s,o]of r.entries()){const i=await n.sendReq({type:"req",id:`external-send-${g()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,text:o,idempotencyKey:r.length>1?`${e.idempotencyKey}:${s+1}`:e.idempotencyKey}});if(!i.ok)return{ok:!1,error:i.error||"External send failed"};t.push(i.payload)}if(e.attachment){const s=await n.sendReq({type:"req",id:`external-send-${g()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,attachment:e.attachment,idempotencyKey:r.length?`${e.idempotencyKey}:attachment`:e.idempotencyKey}});if(!s.ok)return{ok:!1,error:s.error||"External send failed"};t.push(s.payload)}return{ok:!0,payload:t.length===1?t[0]:t}}wakeManagerForWorker(e,r,n,t){const s=this.registry.getManager(e);if(!s)return;const o=`\u4F60\u7BA1\u7406\u7684 worker \u5DF2\u7ED3\u675F\u3002
751
3
 
752
- Worker: ${worker.sessionId}
753
- 状态:${state}
754
- 最终结果:
755
- ${message || worker.summary || '(无可见摘要)'}
4
+ Worker: ${r.sessionId}
5
+ \u72B6\u6001\uFF1A${n}
6
+ \u6700\u7EC8\u7ED3\u679C\uFF1A
7
+ ${t||r.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}
756
8
 
757
- 请决定下一步:继续等待、创建/指派 worker、停止 worker、询问用户,或向用户汇报。`;
758
- void this.interruptAndResumeManager(manager, prompt, state === 'final' ? 'worker.final' : `worker.${state}`);
759
- }
760
- handleExternalMessage(managerSessionId, event) {
761
- const config = this.channelRuntime.getChannelById(event.channelId)
762
- ?? this.channelRuntime.getManagerChannel(managerSessionId, event.channelType);
763
- const externalChannel = this.channelRuntime.getChannelStatusById(event.channelId)
764
- ?? this.channelRuntime.getManagerChannelStatus(managerSessionId);
765
- const manager = this.registry.getManager(managerSessionId);
766
- const agentType = (config?.agentType || (manager ? 'manager' : 'codex'));
767
- const workDir = config?.workDir || manager?.workDir || process.cwd();
768
- const agentSessionId = config?.agentSessionId ?? manager?.agentSessionId ?? null;
769
- const modelId = config?.modelId || manager?.modelId || '';
770
- const attachmentInputs = externalAttachmentsForAgent(event.attachments);
771
- const attachmentSummary = externalAttachmentSummary(event.attachments, event.text);
772
- const mentionSummary = event.isMentioned ? '提及:是' : '';
773
- const visibleReplyTarget = event.replyTarget ? `回复目标:${event.replyTarget}` : '';
774
- const visibleConversation = event.channelType === 'wechat-rpa' && event.conversationName
775
- ? `来源群:${event.conversationName}`
776
- : '';
777
- const visibleBody = [visibleConversation, event.text, attachmentSummary, mentionSummary, visibleReplyTarget].filter(Boolean).join('\n');
778
- const visibleMessage = visibleBody
779
- ? `外部消息 / ${event.sender.name || event.sender.id}\n${visibleBody}`
780
- : `外部消息 / ${event.sender.name || event.sender.id}`;
781
- this.registry.createReplyTarget({
782
- managerSessionId,
783
- channelId: event.channelId,
784
- conversationId: event.conversationId,
785
- messageId: event.messageId,
786
- });
787
- void this.dispatchExternalMessage({
788
- sessionId: managerSessionId,
789
- agentType,
790
- workDir,
791
- agentSessionId,
792
- modelId,
793
- text: visibleMessage,
794
- attachments: attachmentInputs,
795
- externalChannel: externalChannelForChatEnqueue(externalChannel),
796
- replyTarget: event.replyTarget,
797
- });
798
- }
799
- async dispatchExternalMessage(input) {
800
- await this.opts.dispatchReq({
801
- type: 'req',
802
- id: `external-enqueue-${randomUUID()}`,
803
- method: 'chat.enqueue',
804
- params: {
805
- sessionId: input.sessionId,
806
- text: input.text,
807
- agentType: input.agentType,
808
- workDir: input.workDir,
809
- agentSessionId: input.agentSessionId,
810
- modelId: input.modelId,
811
- origin: 'external',
812
- attachments: input.attachments,
813
- externalChannel: input.externalChannel ?? null,
814
- replyTarget: input.replyTarget,
815
- },
816
- });
817
- }
818
- scanWorkerHealth() {
819
- const now = Date.now();
820
- const registry = this.registry.load();
821
- for (const worker of Object.values(registry.workers)) {
822
- if (worker.status !== 'running')
823
- continue;
824
- const ageMs = now - Date.parse(worker.createdAt);
825
- if (ageMs < 10 * 60_000)
826
- continue;
827
- const lastHealthMs = worker.healthNotifiedAt ? Date.parse(worker.healthNotifiedAt) : 0;
828
- if (lastHealthMs && now - lastHealthMs < 10 * 60_000)
829
- continue;
830
- const manager = registry.managers[worker.managedBy];
831
- if (!manager)
832
- continue;
833
- const inactiveMinutes = Math.max(0, Math.floor((now - Date.parse(worker.lastActivityAt)) / 60_000));
834
- const prompt = `Worker ${worker.sessionId} 已运行 ${Math.floor(ageMs / 60_000)} 分钟,尚未结束。
9
+ \u8BF7\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF1A\u7EE7\u7EED\u7B49\u5F85\u3001\u521B\u5EFA/\u6307\u6D3E worker\u3001\u505C\u6B62 worker\u3001\u8BE2\u95EE\u7528\u6237\uFF0C\u6216\u5411\u7528\u6237\u6C47\u62A5\u3002`;this.interruptAndResumeManager(s,o,n==="final"?"worker.final":`worker.${n}`)}handleExternalMessage(e,r){const n=this.channelRuntime.getChannelById(r.channelId)??this.channelRuntime.getManagerChannel(e,r.channelType),t=this.channelRuntime.getChannelStatusById(r.channelId)??this.channelRuntime.getManagerChannelStatus(e),s=this.registry.getManager(e),o=n?.agentType||(s?"manager":"codex"),i=n?.workDir||s?.workDir||process.cwd(),l=n?.agentSessionId??s?.agentSessionId??null,c=n?.modelId||s?.modelId||"",d=te(r.attachments),h=re(r.attachments,r.text),p=r.isMentioned?"\u63D0\u53CA\uFF1A\u662F":"",S=r.replyTarget?`\u56DE\u590D\u76EE\u6807\uFF1A${r.replyTarget}`:"",w=[r.channelType==="wechat-rpa"&&r.conversationName?`\u6765\u6E90\u7FA4\uFF1A${r.conversationName}`:"",r.text,h,p,S].filter(Boolean).join(`
10
+ `),m=w?`\u5916\u90E8\u6D88\u606F / ${r.sender.name||r.sender.id}
11
+ ${w}`:`\u5916\u90E8\u6D88\u606F / ${r.sender.name||r.sender.id}`;this.registry.createReplyTarget({managerSessionId:e,channelId:r.channelId,conversationId:r.conversationId,messageId:r.messageId}),this.dispatchExternalMessage({sessionId:e,agentType:o,workDir:i,agentSessionId:l,modelId:c,text:m,attachments:d,externalChannel:ae(t),replyTarget:r.replyTarget})}async dispatchExternalMessage(e){await this.opts.dispatchReq({type:"req",id:`external-enqueue-${g()}`,method:"chat.enqueue",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,origin:"external",attachments:e.attachments,externalChannel:e.externalChannel??null,replyTarget:e.replyTarget}})}scanWorkerHealth(){const e=Date.now(),r=this.registry.load();for(const n of Object.values(r.workers)){if(n.status!=="running")continue;const t=e-Date.parse(n.createdAt);if(t<10*6e4)continue;const s=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(s&&e-s<10*6e4)continue;const o=r.managers[n.managedBy];if(!o)continue;const i=Math.max(0,Math.floor((e-Date.parse(n.lastActivityAt))/6e4)),l=`Worker ${n.sessionId} \u5DF2\u8FD0\u884C ${Math.floor(t/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
835
12
 
836
- 当前可见进展:
837
- - 最近文本摘要:${worker.summary || '(无可见摘要)'}
838
- - 最近活动时间:${worker.lastActivityAt}
839
- - 最近 ${inactiveMinutes} 分钟没有新活动。
13
+ \u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
14
+ - \u6700\u8FD1\u6587\u672C\u6458\u8981\uFF1A${n.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}
15
+ - \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${n.lastActivityAt}
16
+ - \u6700\u8FD1 ${i} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002
840
17
 
841
- 请决定是否继续等待、询问用户、停止 worker、或创建其他 worker 协助。`;
842
- this.registry.updateWorker(worker.sessionId, {
843
- healthNotifiedAt: new Date(now).toISOString(),
844
- lastHealthSummary: prompt,
845
- });
846
- if (manager.status === 'idle') {
847
- void this.dispatchChatSend(manager.sessionId, 'manager', manager.workDir, prompt, manager.agentSessionId, manager.modelId);
848
- }
849
- }
850
- }
851
- async interruptAndResumeManager(manager, prompt, reason) {
852
- const runtime = this.opts.getRuntime();
853
- const active = runtime.sessions.get(manager.sessionId);
854
- if (active?.currentRunId) {
855
- this.registry.upsertManager({ ...manager, status: 'interrupting' });
856
- await this.opts.dispatchReq({ type: 'req', id: `manager-interrupt-${randomUUID()}`, method: 'chat.abort', params: { sessionId: manager.sessionId } });
857
- }
858
- const continuation = reason ? `事件类型:${reason}\n\n${prompt}` : prompt;
859
- await this.dispatchChatSend(manager.sessionId, 'manager', manager.workDir, continuation, manager.agentSessionId, manager.modelId);
860
- }
861
- bindManagerAdapterEvents(sessionId, adapter) {
862
- adapter.on('agentEvent', (event) => {
863
- if (event.state === 'start' && event.agentSessionId) {
864
- const manager = this.registry.getManager(sessionId);
865
- if (manager)
866
- this.noteManagerAgentSession(sessionId, event.agentSessionId, manager.workDir, manager.modelId);
867
- }
868
- if (event.state === 'start')
869
- this.updateManagerStatus(sessionId, 'running');
870
- if (event.state === 'final' || event.state === 'error' || event.state === 'aborted') {
871
- this.updateManagerStatus(sessionId, 'idle');
872
- }
873
- });
874
- }
875
- updateManagerStatus(sessionId, status) {
876
- const manager = this.registry.getManager(sessionId);
877
- if (!manager)
878
- return;
879
- this.registry.upsertManager({ ...manager, status });
880
- }
881
- getExternalChannelStatus(managerSessionId) {
882
- return this.channelRuntime.getManagerChannelStatus(managerSessionId);
883
- }
884
- getManagerExternalChannelSystemPrompt(managerSessionId) {
885
- return this.channelRuntime
886
- .listManagerExternalChannels(managerSessionId)
887
- .map((channel) => buildExternalChannelInstructions(channel, undefined, managerSessionId, 'manager'))
888
- .join('\n\n')
889
- .trim();
890
- }
891
- }
892
- function parseWeChatRpaGroups(value) {
893
- if (!Array.isArray(value))
894
- return [];
895
- return value
896
- .map((item) => ({ name: String(item?.name || '').trim() }))
897
- .filter((item) => item.name);
898
- }
899
- function parseWeChatRpaSource(value) {
900
- return value === 'macos-flow' || value === 'macos-probe' || value === 'windows-visual-flow' || value === 'wechat-rpa-lab' || value === 'fixture-jsonl' ? value : undefined;
901
- }
902
- function externalAttachmentsForAgent(attachments) {
903
- const result = attachments
904
- .map((attachment) => externalAttachmentForAgent(attachment))
905
- .filter((item) => item != null);
906
- return result.length ? result : undefined;
907
- }
908
- function externalAttachmentSummary(attachments, existingText) {
909
- const text = existingText || '';
910
- const lines = attachments
911
- .filter((attachment) => !externalAttachmentForAgent(attachment))
912
- .map((attachment) => {
913
- const label = attachment.name || attachment.type || 'attachment';
914
- const status = externalAttachmentUnavailableStatus(attachment);
915
- return `附件:${attachment.type || 'file'} ${label} (${status})`;
916
- })
917
- .filter((line) => !text.includes(line));
918
- return lines.join('\n');
919
- }
920
- function externalAttachmentForAgent(attachment) {
921
- if (attachment.localPath && canUseOriginalLocalAttachment(attachment) && isReadableLocalAttachment(attachment.localPath)) {
922
- return {
923
- path: attachment.localPath,
924
- name: attachment.name || path.basename(attachment.localPath) || 'attachment',
925
- mimeType: attachment.mimeType || mimeTypeFromExternalAttachment(attachment),
926
- };
927
- }
928
- if (attachment.url && canUseOriginalUrlAttachment(attachment)) {
929
- return {
930
- path: attachment.url,
931
- name: attachment.name || attachment.url.split('/').filter(Boolean).at(-1) || 'attachment',
932
- mimeType: attachment.mimeType || mimeTypeFromExternalAttachment(attachment),
933
- };
934
- }
935
- if (attachment.thumbnailPath && isReadableLocalAttachment(attachment.thumbnailPath)) {
936
- return {
937
- path: attachment.thumbnailPath,
938
- name: attachment.name ? `${attachment.name}-preview.png` : path.basename(attachment.thumbnailPath) || 'preview.png',
939
- mimeType: 'image/png',
940
- };
941
- }
942
- return null;
943
- }
944
- function externalAttachmentUnavailableStatus(attachment) {
945
- if (attachment.providerError)
946
- return attachment.providerError;
947
- if (attachment.localPath && (!attachment.availability || attachment.availability === 'edge-local'))
948
- return 'edge-local-unavailable';
949
- return attachment.availability || 'metadata-only';
950
- }
951
- function externalChannelForChatEnqueue(channel) {
952
- if (!channel)
953
- return null;
954
- if (channel.type !== 'wechat-rpa')
955
- return channel;
956
- return {
957
- configured: channel.configured,
958
- connected: channel.connected,
959
- type: channel.type,
960
- channelId: channel.channelId,
961
- name: channel.name,
962
- canReply: channel.canReply,
963
- systemPrompt: channel.systemPrompt,
964
- wechatRpaSource: channel.wechatRpaSource,
965
- wechatRpaGroups: channel.wechatRpaGroups,
966
- pollIntervalMs: channel.pollIntervalMs,
967
- recentLimit: channel.recentLimit,
968
- idleSeconds: channel.idleSeconds,
969
- forceForeground: channel.forceForeground,
970
- noRestore: channel.noRestore,
971
- downloadAttachments: channel.downloadAttachments,
972
- selfNickname: channel.selfNickname,
973
- wechatRpaRuntimeState: channel.wechatRpaRuntimeState,
974
- wechatRpaLastMessageAt: channel.wechatRpaLastMessageAt,
975
- wechatRpaPendingReplyCount: channel.wechatRpaPendingReplyCount,
976
- wechatRpaLastError: channel.wechatRpaLastError,
977
- };
978
- }
979
- function canUseOriginalLocalAttachment(attachment) {
980
- return !attachment.providerError
981
- && (!attachment.availability || attachment.availability === 'edge-local');
982
- }
983
- function canUseOriginalUrlAttachment(attachment) {
984
- return !attachment.providerError
985
- && (!attachment.availability || attachment.availability === 'server-url');
986
- }
987
- function isReadableLocalAttachment(filePath) {
988
- try {
989
- return fs.statSync(filePath).isFile();
990
- }
991
- catch {
992
- return false;
993
- }
994
- }
995
- function mimeTypeFromExternalAttachment(attachment) {
996
- if (attachment.type === 'image')
997
- return 'image/*';
998
- if (attachment.type === 'video')
999
- return 'video/*';
1000
- if (attachment.type === 'audio')
1001
- return 'audio/*';
1002
- return 'application/octet-stream';
1003
- }
1004
- function optionalNumber(value) {
1005
- const number = Number(value);
1006
- return Number.isFinite(number) ? number : undefined;
1007
- }
18
+ \u8BF7\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u7B49\u5F85\u3001\u8BE2\u95EE\u7528\u6237\u3001\u505C\u6B62 worker\u3001\u6216\u521B\u5EFA\u5176\u4ED6 worker \u534F\u52A9\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:l}),o.status==="idle"&&this.dispatchChatSend(o.sessionId,"manager",o.workDir,l,o.agentSessionId,o.modelId)}}async interruptAndResumeManager(e,r,n){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&(this.registry.upsertManager({...e,status:"interrupting"}),await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${g()}`,method:"chat.abort",params:{sessionId:e.sessionId}}));const o=n?`\u4E8B\u4EF6\u7C7B\u578B\uFF1A${n}
19
+
20
+ ${r}`:r;await this.dispatchChatSend(e.sessionId,"manager",e.workDir,o,e.agentSessionId,e.modelId)}bindManagerAdapterEvents(e,r){r.on("agentEvent",n=>{if(n.state==="start"&&n.agentSessionId){const t=this.registry.getManager(e);t&&this.noteManagerAgentSession(e,n.agentSessionId,t.workDir,t.modelId)}n.state==="start"&&this.updateManagerStatus(e,"running"),(n.state==="final"||n.state==="error"||n.state==="aborted")&&this.updateManagerStatus(e,"idle")})}updateManagerStatus(e,r){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,status:r})}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(r=>j(r,void 0,e,"manager")).join(`
21
+
22
+ `).trim()}}function Z(a){return Array.isArray(a)?a.map(e=>({name:String(e?.name||"").trim()})).filter(e=>e.name):[]}function ee(a){return a==="macos-flow"||a==="macos-probe"||a==="windows-visual-flow"||a==="wechat-rpa-lab"||a==="fixture-jsonl"?a:void 0}function te(a){const e=a.map(r=>$(r)).filter(r=>r!=null);return e.length?e:void 0}function re(a,e){const r=e||"";return a.filter(t=>!$(t)).map(t=>{const s=t.name||t.type||"attachment",o=ne(t);return`\u9644\u4EF6\uFF1A${t.type||"file"} ${s} (${o})`}).filter(t=>!r.includes(t)).join(`
23
+ `)}function $(a){return a.localPath&&se(a)&&P(a.localPath)?{path:a.localPath,name:a.name||I.basename(a.localPath)||"attachment",mimeType:a.mimeType||D(a)}:a.url&&ie(a)?{path:a.url,name:a.name||a.url.split("/").filter(Boolean).at(-1)||"attachment",mimeType:a.mimeType||D(a)}:a.thumbnailPath&&P(a.thumbnailPath)?{path:a.thumbnailPath,name:a.name?`${a.name}-preview.png`:I.basename(a.thumbnailPath)||"preview.png",mimeType:"image/png"}:null}function ne(a){return a.providerError?a.providerError:a.localPath&&(!a.availability||a.availability==="edge-local")?"edge-local-unavailable":a.availability||"metadata-only"}function ae(a){return a?a.type!=="wechat-rpa"?a:{configured:a.configured,connected:a.connected,type:a.type,channelId:a.channelId,name:a.name,canReply:a.canReply,systemPrompt:a.systemPrompt,wechatRpaSource:a.wechatRpaSource,wechatRpaGroups:a.wechatRpaGroups,pollIntervalMs:a.pollIntervalMs,recentLimit:a.recentLimit,idleSeconds:a.idleSeconds,forceForeground:a.forceForeground,noRestore:a.noRestore,downloadAttachments:a.downloadAttachments,selfNickname:a.selfNickname,wechatRpaPrivacyConsentAccepted:a.wechatRpaPrivacyConsentAccepted,wechatRpaServerDecisionAvailable:a.wechatRpaServerDecisionAvailable,wechatRpaPreflightChecks:a.wechatRpaPreflightChecks,wechatRpaRuntimeState:a.wechatRpaRuntimeState,wechatRpaLastMessageAt:a.wechatRpaLastMessageAt,wechatRpaPendingReplyCount:a.wechatRpaPendingReplyCount,wechatRpaLastError:a.wechatRpaLastError}:null}function se(a){return!a.providerError&&(!a.availability||a.availability==="edge-local")}function ie(a){return!a.providerError&&(!a.availability||a.availability==="server-url")}function P(a){try{return k.statSync(a).isFile()}catch{return!1}}function D(a){return a.type==="image"?"image/*":a.type==="video"?"video/*":a.type==="audio"?"audio/*":"application/octet-stream"}function b(a){const e=Number(a);return Number.isFinite(e)?e:void 0}export{Se as ManagerRuntimeService,Ie as getManagerRuntimeService,ke as setManagerRuntimeService};