shennian 0.2.89 → 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 (118) hide show
  1. package/dist/assets/wechat-channel/macos/manifest.json +13 -4
  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.js +1 -19
  8. package/dist/src/agents/claude.js +8 -305
  9. package/dist/src/agents/codex-control.js +2 -188
  10. package/dist/src/agents/codex-utils.js +7 -200
  11. package/dist/src/agents/codex.js +15 -916
  12. package/dist/src/agents/command-spec.js +2 -413
  13. package/dist/src/agents/config-status.js +1 -226
  14. package/dist/src/agents/cursor.js +1 -249
  15. package/dist/src/agents/custom.js +4 -271
  16. package/dist/src/agents/detect.js +1 -56
  17. package/dist/src/agents/external-channel-instructions.js +10 -94
  18. package/dist/src/agents/gemini.js +1 -173
  19. package/dist/src/agents/manager.js +13 -157
  20. package/dist/src/agents/model-registry/cache.js +1 -37
  21. package/dist/src/agents/model-registry/discovery.js +2 -187
  22. package/dist/src/agents/model-registry/parsers.js +4 -447
  23. package/dist/src/agents/model-registry/runner.js +1 -30
  24. package/dist/src/agents/model-registry/service.js +1 -78
  25. package/dist/src/agents/model-registry/types.js +1 -8
  26. package/dist/src/agents/model-registry.js +1 -18
  27. package/dist/src/agents/openclaw.js +2 -275
  28. package/dist/src/agents/opencode.js +1 -231
  29. package/dist/src/agents/pi-context.js +12 -217
  30. package/dist/src/agents/pi.js +14 -723
  31. package/dist/src/agents/platform-instructions.js +9 -54
  32. package/dist/src/channels/base.js +1 -3
  33. package/dist/src/channels/registry.js +1 -30
  34. package/dist/src/channels/reply-split.js +10 -89
  35. package/dist/src/channels/runtime.js +5 -564
  36. package/dist/src/channels/secret-registry.js +1 -46
  37. package/dist/src/channels/websocket.js +8 -378
  38. package/dist/src/channels/wechat-channel/anchor.js +1 -65
  39. package/dist/src/channels/wechat-channel/client.js +1 -96
  40. package/dist/src/channels/wechat-channel/cooldown.js +1 -38
  41. package/dist/src/channels/wechat-channel/fingerprint.js +1 -71
  42. package/dist/src/channels/wechat-channel/helper-assets.d.ts +10 -1
  43. package/dist/src/channels/wechat-channel/helper-assets.js +1 -68
  44. package/dist/src/channels/wechat-channel/helper-client.js +3 -149
  45. package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
  46. package/dist/src/channels/wechat-channel/helper-protocol.js +1 -115
  47. package/dist/src/channels/wechat-channel/index.d.ts +1 -0
  48. package/dist/src/channels/wechat-channel/index.js +1 -19
  49. package/dist/src/channels/wechat-channel/ledger.js +1 -54
  50. package/dist/src/channels/wechat-channel/media-resolver.js +1 -181
  51. package/dist/src/channels/wechat-channel/message-key.js +1 -105
  52. package/dist/src/channels/wechat-channel/observer.js +1 -118
  53. package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +3 -0
  54. package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -112
  55. package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
  56. package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
  57. package/dist/src/channels/wechat-channel/preflight.js +1 -48
  58. package/dist/src/channels/wechat-channel/runner.js +1 -84
  59. package/dist/src/channels/wechat-channel/runtime.js +1 -66
  60. package/dist/src/channels/wechat-channel/scheduler.d.ts +5 -0
  61. package/dist/src/channels/wechat-channel/scheduler.js +1 -152
  62. package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
  63. package/dist/src/channels/wechat-rpa/macos.js +6 -48
  64. package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
  65. package/dist/src/channels/wechat-rpa.js +6 -1028
  66. package/dist/src/channels/wecom.js +4 -357
  67. package/dist/src/commands/agent.js +6 -131
  68. package/dist/src/commands/daemon-windows.js +8 -48
  69. package/dist/src/commands/daemon.js +19 -1013
  70. package/dist/src/commands/external-attachments.js +1 -51
  71. package/dist/src/commands/external.js +1 -137
  72. package/dist/src/commands/manager.js +2 -391
  73. package/dist/src/commands/pair-qr.js +1 -6
  74. package/dist/src/commands/pair.js +9 -287
  75. package/dist/src/commands/tools.js +1 -34
  76. package/dist/src/commands/upgrade.js +1 -198
  77. package/dist/src/config/index.js +1 -35
  78. package/dist/src/daemon-log.js +6 -58
  79. package/dist/src/env-path.js +1 -64
  80. package/dist/src/fs/boundary.js +1 -126
  81. package/dist/src/fs/handler.js +1 -130
  82. package/dist/src/fs/security.js +1 -32
  83. package/dist/src/fs/text-decoder.js +1 -110
  84. package/dist/src/index.js +2 -404
  85. package/dist/src/log-reporter.js +1 -16
  86. package/dist/src/manager/prompt.js +29 -34
  87. package/dist/src/manager/registry.js +2 -269
  88. package/dist/src/manager/runtime.js +19 -1007
  89. package/dist/src/native-fusion/config.js +1 -5
  90. package/dist/src/native-fusion/opencode-parser.js +3 -123
  91. package/dist/src/native-fusion/parser-common.js +8 -264
  92. package/dist/src/native-fusion/parsers.js +8 -729
  93. package/dist/src/native-fusion/service.js +2 -225
  94. package/dist/src/native-fusion/state.js +1 -22
  95. package/dist/src/native-fusion/types.js +1 -1
  96. package/dist/src/region.js +1 -88
  97. package/dist/src/relay/client.js +1 -343
  98. package/dist/src/session/archive-zip.js +1 -220
  99. package/dist/src/session/handlers/agent-config.js +1 -150
  100. package/dist/src/session/handlers/agents.js +1 -55
  101. package/dist/src/session/handlers/chat.js +2 -751
  102. package/dist/src/session/handlers/control.js +1 -55
  103. package/dist/src/session/handlers/fs.js +1 -783
  104. package/dist/src/session/handlers/session-refresh.js +1 -47
  105. package/dist/src/session/handlers/skills.js +1 -121
  106. package/dist/src/session/handlers/title.js +1 -60
  107. package/dist/src/session/handlers/tool-detail.js +1 -218
  108. package/dist/src/session/manager.js +1 -319
  109. package/dist/src/session/projection.js +1 -54
  110. package/dist/src/session/queue.js +4 -317
  111. package/dist/src/session/remote-attachments.js +1 -72
  112. package/dist/src/session/store.js +3 -109
  113. package/dist/src/session/types.js +1 -4
  114. package/dist/src/skills/registry.js +15 -148
  115. package/dist/src/skills/setup.js +1 -101
  116. package/dist/src/tools/markdown-to-pdf.js +10 -346
  117. package/dist/src/upgrade/engine.js +3 -347
  118. package/package.json +3 -2
@@ -1,1011 +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
- privacyConsentAccepted: body.privacyConsentAccepted === undefined ? undefined : Boolean(body.privacyConsentAccepted),
668
- flowScriptPath: typeof body.flowScriptPath === 'string' ? body.flowScriptPath : undefined,
669
- });
670
- this.registry.upsertManager({
671
- ...manager,
672
- status: manager.status,
673
- });
674
- this.broadcastManagerChannelStatus(managerSessionId);
675
- json(res, 200, { ok: true, channel });
676
- return;
677
- }
678
- if (url.pathname === '/wechat-rpa/channel/sync') {
679
- const { channel, messages } = await this.channelRuntime.syncManagerWeChatRpaChannel(managerSessionId);
680
- this.broadcastManagerChannelStatus(managerSessionId);
681
- json(res, 200, { ok: true, channel, messages });
682
- return;
683
- }
684
- json(res, 404, { ok: false, error: `Unknown manager IPC path: ${url.pathname}` });
685
- }
686
- catch (err) {
687
- json(res, 400, { ok: false, error: err instanceof Error ? err.message : String(err) });
688
- }
689
- }
690
- async dispatchChatSend(sessionId, agentType, workDir, text, agentSessionId, modelId) {
691
- await this.opts.dispatchReq({
692
- type: 'req',
693
- id: `manager-send-${randomUUID()}`,
694
- method: 'chat.send',
695
- params: { sessionId, text, agentType, workDir, agentSessionId, modelId },
696
- });
697
- }
698
- async dispatchChatEnqueue(sessionId, agentType, workDir, text, agentSessionId, modelId) {
699
- await this.opts.dispatchReq({
700
- type: 'req',
701
- id: `manager-enqueue-${randomUUID()}`,
702
- method: 'chat.enqueue',
703
- params: { sessionId, text, agentType, workDir, agentSessionId, modelId },
704
- });
705
- }
706
- async sendManagedWeComReply(input) {
707
- const parts = splitExternalReplyText(input.text);
708
- if (!parts.length && !input.attachment)
709
- return { ok: false, error: 'text or attachment is required' };
710
- const client = this.opts.getRuntime().client;
711
- if (!client || typeof client.sendReq !== 'function') {
712
- return { ok: false, error: 'Relay is not connected' };
713
- }
714
- const payloads = [];
715
- for (const [index, text] of parts.entries()) {
716
- const frame = await client.sendReq({
717
- type: 'req',
718
- id: `external-send-${randomUUID()}`,
719
- method: 'external.send',
720
- params: {
721
- managerSessionId: input.managerSessionId,
722
- text,
723
- idempotencyKey: parts.length > 1 ? `${input.idempotencyKey}:${index + 1}` : input.idempotencyKey,
724
- },
725
- });
726
- if (!frame.ok)
727
- return { ok: false, error: frame.error || 'External send failed' };
728
- payloads.push(frame.payload);
729
- }
730
- if (input.attachment) {
731
- const frame = await client.sendReq({
732
- type: 'req',
733
- id: `external-send-${randomUUID()}`,
734
- method: 'external.send',
735
- params: {
736
- managerSessionId: input.managerSessionId,
737
- attachment: input.attachment,
738
- idempotencyKey: parts.length ? `${input.idempotencyKey}:attachment` : input.idempotencyKey,
739
- },
740
- });
741
- if (!frame.ok)
742
- return { ok: false, error: frame.error || 'External send failed' };
743
- payloads.push(frame.payload);
744
- }
745
- return { ok: true, payload: payloads.length === 1 ? payloads[0] : payloads };
746
- }
747
- wakeManagerForWorker(managerSessionId, worker, state, message) {
748
- const manager = this.registry.getManager(managerSessionId);
749
- if (!manager)
750
- return;
751
- 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
752
3
 
753
- Worker: ${worker.sessionId}
754
- 状态:${state}
755
- 最终结果:
756
- ${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)"}
757
8
 
758
- 请决定下一步:继续等待、创建/指派 worker、停止 worker、询问用户,或向用户汇报。`;
759
- void this.interruptAndResumeManager(manager, prompt, state === 'final' ? 'worker.final' : `worker.${state}`);
760
- }
761
- handleExternalMessage(managerSessionId, event) {
762
- const config = this.channelRuntime.getChannelById(event.channelId)
763
- ?? this.channelRuntime.getManagerChannel(managerSessionId, event.channelType);
764
- const externalChannel = this.channelRuntime.getChannelStatusById(event.channelId)
765
- ?? this.channelRuntime.getManagerChannelStatus(managerSessionId);
766
- const manager = this.registry.getManager(managerSessionId);
767
- const agentType = (config?.agentType || (manager ? 'manager' : 'codex'));
768
- const workDir = config?.workDir || manager?.workDir || process.cwd();
769
- const agentSessionId = config?.agentSessionId ?? manager?.agentSessionId ?? null;
770
- const modelId = config?.modelId || manager?.modelId || '';
771
- const attachmentInputs = externalAttachmentsForAgent(event.attachments);
772
- const attachmentSummary = externalAttachmentSummary(event.attachments, event.text);
773
- const mentionSummary = event.isMentioned ? '提及:是' : '';
774
- const visibleReplyTarget = event.replyTarget ? `回复目标:${event.replyTarget}` : '';
775
- const visibleConversation = event.channelType === 'wechat-rpa' && event.conversationName
776
- ? `来源群:${event.conversationName}`
777
- : '';
778
- const visibleBody = [visibleConversation, event.text, attachmentSummary, mentionSummary, visibleReplyTarget].filter(Boolean).join('\n');
779
- const visibleMessage = visibleBody
780
- ? `外部消息 / ${event.sender.name || event.sender.id}\n${visibleBody}`
781
- : `外部消息 / ${event.sender.name || event.sender.id}`;
782
- this.registry.createReplyTarget({
783
- managerSessionId,
784
- channelId: event.channelId,
785
- conversationId: event.conversationId,
786
- messageId: event.messageId,
787
- });
788
- void this.dispatchExternalMessage({
789
- sessionId: managerSessionId,
790
- agentType,
791
- workDir,
792
- agentSessionId,
793
- modelId,
794
- text: visibleMessage,
795
- attachments: attachmentInputs,
796
- externalChannel: externalChannelForChatEnqueue(externalChannel),
797
- replyTarget: event.replyTarget,
798
- });
799
- }
800
- async dispatchExternalMessage(input) {
801
- await this.opts.dispatchReq({
802
- type: 'req',
803
- id: `external-enqueue-${randomUUID()}`,
804
- method: 'chat.enqueue',
805
- params: {
806
- sessionId: input.sessionId,
807
- text: input.text,
808
- agentType: input.agentType,
809
- workDir: input.workDir,
810
- agentSessionId: input.agentSessionId,
811
- modelId: input.modelId,
812
- origin: 'external',
813
- attachments: input.attachments,
814
- externalChannel: input.externalChannel ?? null,
815
- replyTarget: input.replyTarget,
816
- },
817
- });
818
- }
819
- scanWorkerHealth() {
820
- const now = Date.now();
821
- const registry = this.registry.load();
822
- for (const worker of Object.values(registry.workers)) {
823
- if (worker.status !== 'running')
824
- continue;
825
- const ageMs = now - Date.parse(worker.createdAt);
826
- if (ageMs < 10 * 60_000)
827
- continue;
828
- const lastHealthMs = worker.healthNotifiedAt ? Date.parse(worker.healthNotifiedAt) : 0;
829
- if (lastHealthMs && now - lastHealthMs < 10 * 60_000)
830
- continue;
831
- const manager = registry.managers[worker.managedBy];
832
- if (!manager)
833
- continue;
834
- const inactiveMinutes = Math.max(0, Math.floor((now - Date.parse(worker.lastActivityAt)) / 60_000));
835
- 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
836
12
 
837
- 当前可见进展:
838
- - 最近文本摘要:${worker.summary || '(无可见摘要)'}
839
- - 最近活动时间:${worker.lastActivityAt}
840
- - 最近 ${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
841
17
 
842
- 请决定是否继续等待、询问用户、停止 worker、或创建其他 worker 协助。`;
843
- this.registry.updateWorker(worker.sessionId, {
844
- healthNotifiedAt: new Date(now).toISOString(),
845
- lastHealthSummary: prompt,
846
- });
847
- if (manager.status === 'idle') {
848
- void this.dispatchChatSend(manager.sessionId, 'manager', manager.workDir, prompt, manager.agentSessionId, manager.modelId);
849
- }
850
- }
851
- }
852
- async interruptAndResumeManager(manager, prompt, reason) {
853
- const runtime = this.opts.getRuntime();
854
- const active = runtime.sessions.get(manager.sessionId);
855
- if (active?.currentRunId) {
856
- this.registry.upsertManager({ ...manager, status: 'interrupting' });
857
- await this.opts.dispatchReq({ type: 'req', id: `manager-interrupt-${randomUUID()}`, method: 'chat.abort', params: { sessionId: manager.sessionId } });
858
- }
859
- const continuation = reason ? `事件类型:${reason}\n\n${prompt}` : prompt;
860
- await this.dispatchChatSend(manager.sessionId, 'manager', manager.workDir, continuation, manager.agentSessionId, manager.modelId);
861
- }
862
- bindManagerAdapterEvents(sessionId, adapter) {
863
- adapter.on('agentEvent', (event) => {
864
- if (event.state === 'start' && event.agentSessionId) {
865
- const manager = this.registry.getManager(sessionId);
866
- if (manager)
867
- this.noteManagerAgentSession(sessionId, event.agentSessionId, manager.workDir, manager.modelId);
868
- }
869
- if (event.state === 'start')
870
- this.updateManagerStatus(sessionId, 'running');
871
- if (event.state === 'final' || event.state === 'error' || event.state === 'aborted') {
872
- this.updateManagerStatus(sessionId, 'idle');
873
- }
874
- });
875
- }
876
- updateManagerStatus(sessionId, status) {
877
- const manager = this.registry.getManager(sessionId);
878
- if (!manager)
879
- return;
880
- this.registry.upsertManager({ ...manager, status });
881
- }
882
- getExternalChannelStatus(managerSessionId) {
883
- return this.channelRuntime.getManagerChannelStatus(managerSessionId);
884
- }
885
- getManagerExternalChannelSystemPrompt(managerSessionId) {
886
- return this.channelRuntime
887
- .listManagerExternalChannels(managerSessionId)
888
- .map((channel) => buildExternalChannelInstructions(channel, undefined, managerSessionId, 'manager'))
889
- .join('\n\n')
890
- .trim();
891
- }
892
- }
893
- function parseWeChatRpaGroups(value) {
894
- if (!Array.isArray(value))
895
- return [];
896
- return value
897
- .map((item) => ({ name: String(item?.name || '').trim() }))
898
- .filter((item) => item.name);
899
- }
900
- function parseWeChatRpaSource(value) {
901
- return value === 'macos-flow' || value === 'macos-probe' || value === 'windows-visual-flow' || value === 'wechat-rpa-lab' || value === 'fixture-jsonl' ? value : undefined;
902
- }
903
- function externalAttachmentsForAgent(attachments) {
904
- const result = attachments
905
- .map((attachment) => externalAttachmentForAgent(attachment))
906
- .filter((item) => item != null);
907
- return result.length ? result : undefined;
908
- }
909
- function externalAttachmentSummary(attachments, existingText) {
910
- const text = existingText || '';
911
- const lines = attachments
912
- .filter((attachment) => !externalAttachmentForAgent(attachment))
913
- .map((attachment) => {
914
- const label = attachment.name || attachment.type || 'attachment';
915
- const status = externalAttachmentUnavailableStatus(attachment);
916
- return `附件:${attachment.type || 'file'} ${label} (${status})`;
917
- })
918
- .filter((line) => !text.includes(line));
919
- return lines.join('\n');
920
- }
921
- function externalAttachmentForAgent(attachment) {
922
- if (attachment.localPath && canUseOriginalLocalAttachment(attachment) && isReadableLocalAttachment(attachment.localPath)) {
923
- return {
924
- path: attachment.localPath,
925
- name: attachment.name || path.basename(attachment.localPath) || 'attachment',
926
- mimeType: attachment.mimeType || mimeTypeFromExternalAttachment(attachment),
927
- };
928
- }
929
- if (attachment.url && canUseOriginalUrlAttachment(attachment)) {
930
- return {
931
- path: attachment.url,
932
- name: attachment.name || attachment.url.split('/').filter(Boolean).at(-1) || 'attachment',
933
- mimeType: attachment.mimeType || mimeTypeFromExternalAttachment(attachment),
934
- };
935
- }
936
- if (attachment.thumbnailPath && isReadableLocalAttachment(attachment.thumbnailPath)) {
937
- return {
938
- path: attachment.thumbnailPath,
939
- name: attachment.name ? `${attachment.name}-preview.png` : path.basename(attachment.thumbnailPath) || 'preview.png',
940
- mimeType: 'image/png',
941
- };
942
- }
943
- return null;
944
- }
945
- function externalAttachmentUnavailableStatus(attachment) {
946
- if (attachment.providerError)
947
- return attachment.providerError;
948
- if (attachment.localPath && (!attachment.availability || attachment.availability === 'edge-local'))
949
- return 'edge-local-unavailable';
950
- return attachment.availability || 'metadata-only';
951
- }
952
- function externalChannelForChatEnqueue(channel) {
953
- if (!channel)
954
- return null;
955
- if (channel.type !== 'wechat-rpa')
956
- return channel;
957
- return {
958
- configured: channel.configured,
959
- connected: channel.connected,
960
- type: channel.type,
961
- channelId: channel.channelId,
962
- name: channel.name,
963
- canReply: channel.canReply,
964
- systemPrompt: channel.systemPrompt,
965
- wechatRpaSource: channel.wechatRpaSource,
966
- wechatRpaGroups: channel.wechatRpaGroups,
967
- pollIntervalMs: channel.pollIntervalMs,
968
- recentLimit: channel.recentLimit,
969
- idleSeconds: channel.idleSeconds,
970
- forceForeground: channel.forceForeground,
971
- noRestore: channel.noRestore,
972
- downloadAttachments: channel.downloadAttachments,
973
- selfNickname: channel.selfNickname,
974
- wechatRpaPrivacyConsentAccepted: channel.wechatRpaPrivacyConsentAccepted,
975
- wechatRpaServerDecisionAvailable: channel.wechatRpaServerDecisionAvailable,
976
- wechatRpaPreflightChecks: channel.wechatRpaPreflightChecks,
977
- wechatRpaRuntimeState: channel.wechatRpaRuntimeState,
978
- wechatRpaLastMessageAt: channel.wechatRpaLastMessageAt,
979
- wechatRpaPendingReplyCount: channel.wechatRpaPendingReplyCount,
980
- wechatRpaLastError: channel.wechatRpaLastError,
981
- };
982
- }
983
- function canUseOriginalLocalAttachment(attachment) {
984
- return !attachment.providerError
985
- && (!attachment.availability || attachment.availability === 'edge-local');
986
- }
987
- function canUseOriginalUrlAttachment(attachment) {
988
- return !attachment.providerError
989
- && (!attachment.availability || attachment.availability === 'server-url');
990
- }
991
- function isReadableLocalAttachment(filePath) {
992
- try {
993
- return fs.statSync(filePath).isFile();
994
- }
995
- catch {
996
- return false;
997
- }
998
- }
999
- function mimeTypeFromExternalAttachment(attachment) {
1000
- if (attachment.type === 'image')
1001
- return 'image/*';
1002
- if (attachment.type === 'video')
1003
- return 'video/*';
1004
- if (attachment.type === 'audio')
1005
- return 'audio/*';
1006
- return 'application/octet-stream';
1007
- }
1008
- function optionalNumber(value) {
1009
- const number = Number(value);
1010
- return Number.isFinite(number) ? number : undefined;
1011
- }
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};