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,751 +1,2 @@
1
- // @arch docs/architecture/cli/daemon.md#会话管理
2
- // @test src/__tests__/session-manager.test.ts
3
- import os from 'node:os';
4
- import { createAgent } from '../../agents/adapter.js';
5
- import { buildApprovalPendingPayload, buildUserMessagePayload } from '@shennian/wire';
6
- import { reportLog } from '../../log-reporter.js';
7
- import { lookupClaudeTranscriptCwd } from '../../native-fusion/parsers.js';
8
- import { appendMessage, recordSession } from '../store.js';
9
- import { mergeProjectedSessions } from '../projection.js';
10
- import { getManagerRuntimeService } from '../../manager/runtime.js';
11
- import { buildManagedAgentEnv } from '../../agents/config-status.js';
12
- import { materializeRemoteChatAttachments } from '../remote-attachments.js';
13
- function normalizeChatAttachments(value) {
14
- if (!Array.isArray(value))
15
- return undefined;
16
- const attachments = value
17
- .map((item) => {
18
- if (!item || typeof item !== 'object')
19
- return null;
20
- const entry = item;
21
- const path = typeof entry.path === 'string' ? entry.path : '';
22
- const name = typeof entry.name === 'string' ? entry.name : '';
23
- const mimeType = typeof entry.mimeType === 'string' ? entry.mimeType : '';
24
- if (!path || !name || !mimeType)
25
- return null;
26
- const previewData = typeof entry.previewData === 'string' && entry.previewData.trim() ? entry.previewData.trim() : undefined;
27
- return { path, name, mimeType, kind: mimeType.startsWith('image/') ? 'image' : 'file', ...(previewData ? { previewData } : {}) };
28
- })
29
- .filter((item) => item != null);
30
- return attachments.length ? attachments : undefined;
31
- }
32
- function extractSummary(text) {
33
- const newline = text.indexOf('\n');
34
- const end = newline > 0 ? Math.min(newline, 80) : Math.min(text.length, 80);
35
- return text.slice(0, end);
36
- }
37
- function buildRelayAgentPayload(event, sessionId, extra = {}) {
38
- if (event.state === 'tool-call' || event.state === 'tool-result') {
39
- const detailRef = { runId: event.runId, sourceSeq: event.seq };
40
- return {
41
- state: event.state,
42
- runId: event.runId,
43
- seq: event.seq,
44
- sessionId,
45
- detailRef,
46
- ...(event.name ? { name: event.name } : {}),
47
- ...(event.source ? { source: event.source } : {}),
48
- ...(event.agentSessionId ? { agentSessionId: event.agentSessionId } : {}),
49
- ...extra,
50
- };
51
- }
52
- return { ...event, sessionId, ...extra };
53
- }
54
- const SESSION_ACTIVITY_HEARTBEAT_INTERVAL_MS = 30_000;
55
- function runPhaseFromAgentEvent(event) {
56
- if (event.state === 'heartbeat')
57
- return event.runPhase ?? null;
58
- if (event.state === 'tool-call' || event.state === 'tool-result')
59
- return 'tool_running';
60
- if (event.state === 'approval-pending')
61
- return 'waiting_approval';
62
- if (event.state === 'delta')
63
- return event.thinking ? 'thinking' : 'streaming_text';
64
- if (event.state === 'init' || event.state === 'start')
65
- return 'thinking';
66
- return null;
67
- }
68
- function formatAgentSendFailure(agentType, err) {
69
- const raw = err instanceof Error ? err.message : String(err);
70
- if (agentType === 'pi' &&
71
- (raw.includes('429') || raw.includes('daily_quota_exceeded') || raw.includes('nian_quota_exceeded'))) {
72
- return raw.includes('too quickly') || raw.includes('per minute')
73
- ? 'Nian 请求过于频繁,请稍后再试。'
74
- : 'Nian 今日额度已用完,次日自动恢复。';
75
- }
76
- return `Agent send failed: ${raw}`;
77
- }
78
- function getNativeSourceAgentType(agentType, modelId) {
79
- if (agentType !== 'manager')
80
- return agentType;
81
- return modelId === 'claude' ? 'claude' : 'codex';
82
- }
83
- function sendSessionMessageEvent(runtime, envelope, session) {
84
- runtime.client.sendEvent({
85
- type: 'event',
86
- event: 'session.message',
87
- payload: {
88
- sessionId: envelope.sessionId,
89
- message: envelope,
90
- session: {
91
- id: envelope.sessionId,
92
- agentType: session.agentType,
93
- agentSessionId: session.agentSessionId ?? null,
94
- modelId: session.modelId ?? null,
95
- workDir: session.workDir,
96
- status: 'active',
97
- externalChannel: getSessionExternalChannel(runtime, envelope.sessionId, session.agentType),
98
- },
99
- },
100
- });
101
- }
102
- function sendSessionUpdateEvent(runtime, input) {
103
- recordSession({
104
- sessionId: input.sessionId,
105
- agentType: input.agentType,
106
- workDir: input.workDir,
107
- agentSessionId: input.agentSessionId ?? null,
108
- modelId: input.modelId ?? null,
109
- });
110
- runtime.client.sendEvent({
111
- type: 'event',
112
- event: 'session.update',
113
- payload: {
114
- session: {
115
- id: input.sessionId,
116
- agentType: input.agentType,
117
- agentSessionId: input.agentSessionId ?? null,
118
- modelId: input.modelId ?? null,
119
- workDir: input.workDir,
120
- status: 'active',
121
- externalChannel: getSessionExternalChannel(runtime, input.sessionId, input.agentType),
122
- },
123
- },
124
- });
125
- }
126
- function normalizeExternalChannel(value) {
127
- if (!value || typeof value !== 'object')
128
- return null;
129
- const raw = value;
130
- return {
131
- configured: raw.configured === undefined ? undefined : Boolean(raw.configured),
132
- connected: Boolean(raw.connected),
133
- type: typeof raw.type === 'string' ? raw.type : null,
134
- channelId: typeof raw.channelId === 'string' ? raw.channelId : null,
135
- name: typeof raw.name === 'string' ? raw.name : null,
136
- canReply: raw.canReply === undefined || raw.canReply === null ? null : Boolean(raw.canReply),
137
- systemPrompt: typeof raw.systemPrompt === 'string' ? raw.systemPrompt : null,
138
- wechatRpaSource: typeof raw.wechatRpaSource === 'string' ? raw.wechatRpaSource : null,
139
- wechatRpaGroups: Array.isArray(raw.wechatRpaGroups)
140
- ? raw.wechatRpaGroups
141
- .map((item) => ({ name: String(item?.name || '').trim() }))
142
- .filter((item) => item.name)
143
- : null,
144
- pollIntervalMs: Number.isFinite(raw.pollIntervalMs) ? Number(raw.pollIntervalMs) : null,
145
- recentLimit: Number.isFinite(raw.recentLimit) ? Number(raw.recentLimit) : null,
146
- idleSeconds: Number.isFinite(raw.idleSeconds) ? Number(raw.idleSeconds) : null,
147
- forceForeground: raw.forceForeground === undefined || raw.forceForeground === null ? null : Boolean(raw.forceForeground),
148
- noRestore: raw.noRestore === undefined || raw.noRestore === null ? null : Boolean(raw.noRestore),
149
- downloadAttachments: raw.downloadAttachments === undefined || raw.downloadAttachments === null ? null : Boolean(raw.downloadAttachments),
150
- downloadAttachmentsDir: typeof raw.downloadAttachmentsDir === 'string' ? raw.downloadAttachmentsDir : null,
151
- };
152
- }
153
- function externalChannelEnabled(channel) {
154
- return Boolean(channel?.configured ?? channel?.connected);
155
- }
156
- function managedProviderEnv(agentType) {
157
- return buildManagedAgentEnv(agentType);
158
- }
159
- function externalChannelEnv(sessionId, channel, replyTarget) {
160
- if (!externalChannelEnabled(channel))
161
- return {};
162
- const service = getManagerRuntimeService();
163
- const injected = service?.getInjectedEnv(sessionId, null, process.cwd(), 'external') ?? {};
164
- return {
165
- ...injected,
166
- SHENNIAN_EXTERNAL_SESSION_ID: sessionId,
167
- SHENNIAN_MANAGER_SESSION_ID: sessionId,
168
- ...(replyTarget?.trim() ? { SHENNIAN_EXTERNAL_REPLY_TARGET: replyTarget.trim() } : {}),
169
- };
170
- }
171
- function configureAdapterForSession(adapter, sessionId, agentType, channel, replyTarget) {
172
- adapter.configure?.({
173
- sessionId,
174
- externalChannel: channel ?? null,
175
- env: {
176
- ...managedProviderEnv(agentType),
177
- ...externalChannelEnv(sessionId, channel, replyTarget),
178
- },
179
- });
180
- }
181
- function getSessionExternalChannel(runtime, sessionId, agentType) {
182
- const active = runtime.sessions.get(sessionId);
183
- if (active?.externalChannel)
184
- return active.externalChannel;
185
- if (agentType === 'manager')
186
- return runtime.managerRuntime?.getExternalChannelStatus(sessionId) ?? null;
187
- return null;
188
- }
189
- function maybeResolveClaudeImportedWorkDir(agentType, workDir, agentSessionId) {
190
- if (agentType !== 'claude')
191
- return workDir;
192
- if (!agentSessionId)
193
- return workDir;
194
- const trimmed = workDir.trim();
195
- const looksLikeTranscriptSlug = !!trimmed && trimmed.startsWith('-') && !trimmed.includes('/');
196
- if (!looksLikeTranscriptSlug)
197
- return workDir;
198
- return lookupClaudeTranscriptCwd(agentSessionId) ?? workDir;
199
- }
200
- function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
201
- let emittedAgentSessionId = null;
202
- function sendAgentEvent(event, extra = {}) {
203
- runtime.client.sendAgentEvent({
204
- type: 'event',
205
- event: 'agent',
206
- payload: buildRelayAgentPayload(event, sessionId, extra),
207
- seq: event.seq,
208
- id: `agent-evt-${event.runId}-${event.seq}`,
209
- });
210
- }
211
- function stopActivityHeartbeat(activeSession) {
212
- if (!activeSession?.heartbeatTimer)
213
- return;
214
- clearInterval(activeSession.heartbeatTimer);
215
- activeSession.heartbeatTimer = null;
216
- }
217
- function sendActivityHeartbeat(activeSession) {
218
- if (!activeSession.currentRunId || !activeSession.currentRunPhase)
219
- return;
220
- const seq = activeSession.heartbeatSeq++;
221
- runtime.client.sendAgentEvent({
222
- type: 'event',
223
- event: 'agent',
224
- payload: {
225
- state: 'heartbeat',
226
- sessionId,
227
- runId: activeSession.currentRunId,
228
- seq,
229
- runPhase: activeSession.currentRunPhase,
230
- },
231
- seq,
232
- id: `agent-heartbeat-${activeSession.currentRunId}-${seq}-${Date.now()}`,
233
- });
234
- }
235
- function ensureActivityHeartbeat(activeSession) {
236
- if (!activeSession || activeSession.heartbeatTimer)
237
- return;
238
- activeSession.heartbeatTimer = setInterval(() => {
239
- sendActivityHeartbeat(activeSession);
240
- }, SESSION_ACTIVITY_HEARTBEAT_INTERVAL_MS);
241
- activeSession.heartbeatTimer.unref?.();
242
- }
243
- function flushTextBuffer(activeSession) {
244
- const textBuffer = activeSession?.pendingTextEvent;
245
- if (!activeSession || !textBuffer || !textBuffer.text)
246
- return;
247
- sendAgentEvent({
248
- state: 'delta',
249
- runId: textBuffer.runId,
250
- seq: textBuffer.seq,
251
- text: textBuffer.text,
252
- thinking: textBuffer.thinking || undefined,
253
- });
254
- activeSession.pendingTextEvent = null;
255
- }
256
- adapter.on('agentEvent', (event) => {
257
- const activeSession = runtime.sessions.get(sessionId);
258
- const runPhase = runPhaseFromAgentEvent(event);
259
- const isTerminalEvent = event.state === 'final' || event.state === 'error' || event.state === 'aborted';
260
- if (activeSession) {
261
- activeSession.nextEventSeq = event.seq + 1;
262
- if (!isTerminalEvent)
263
- activeSession.currentRunId = event.runId;
264
- if (!isTerminalEvent && runPhase) {
265
- activeSession.currentRunPhase = runPhase;
266
- ensureActivityHeartbeat(activeSession);
267
- }
268
- if (event.agentSessionId)
269
- activeSession.agentSessionId = event.agentSessionId;
270
- }
271
- runtime.managerRuntime?.noteAgentEvent(sessionId, event);
272
- if (event.state !== 'delta') {
273
- reportLog({
274
- level: 'info',
275
- sessionId,
276
- wsEvent: `agent.${event.state}`,
277
- wsDirection: 'out',
278
- metadata: { runId: event.runId, seq: event.seq, agentType },
279
- });
280
- }
281
- if (event.state === 'delta' && event.text && !event.thinking) {
282
- appendMessage(sessionId, {
283
- id: `agent-${event.runId}-${event.seq}`,
284
- sessionId,
285
- role: 'agent',
286
- ts: Date.now(),
287
- payload: event.text,
288
- });
289
- }
290
- else if (event.state === 'tool-call' || event.state === 'tool-result') {
291
- appendMessage(sessionId, {
292
- id: `agent-${event.runId}-${event.seq}`,
293
- sessionId,
294
- role: 'agent',
295
- ts: Date.now(),
296
- payload: JSON.stringify({
297
- v: 1,
298
- type: event.state === 'tool-call' ? 'tool_use' : 'tool_result',
299
- name: event.name,
300
- status: event.state === 'tool-call' ? 'running' : 'completed',
301
- detailRef: { runId: event.runId, sourceSeq: event.seq },
302
- args: event.args,
303
- result: event.result,
304
- }),
305
- });
306
- }
307
- else if (event.state === 'approval-pending') {
308
- appendMessage(sessionId, {
309
- id: `agent-${event.runId || 'run'}-${event.seq}`,
310
- sessionId,
311
- role: 'agent',
312
- ts: Date.now(),
313
- payload: buildApprovalPendingPayload(event.approval),
314
- });
315
- }
316
- else if ((event.state === 'error' || event.state === 'aborted') && event.message) {
317
- appendMessage(sessionId, {
318
- id: `agent-${event.runId || 'run'}-${event.seq}`,
319
- sessionId,
320
- role: 'agent',
321
- ts: Date.now(),
322
- payload: event.message,
323
- });
324
- }
325
- const runKey = `${sessionId}:${event.runId}`;
326
- if (event.state === 'delta' && !event.thinking && event.text) {
327
- runtime.runTextAcc.set(runKey, (runtime.runTextAcc.get(runKey) ?? '') + event.text);
328
- }
329
- if (event.agentSessionId && event.agentSessionId !== emittedAgentSessionId) {
330
- emittedAgentSessionId = event.agentSessionId;
331
- runtime.nativeFusion?.noteManagedSourceSession(sessionId, getNativeSourceAgentType(agentType, event.source), event.agentSessionId);
332
- if (activeSession) {
333
- recordSession({
334
- sessionId,
335
- agentType,
336
- workDir: activeSession.workDir,
337
- agentSessionId: event.agentSessionId,
338
- });
339
- }
340
- runtime.client.sendEvent({
341
- type: 'event',
342
- event: 'session.update',
343
- payload: {
344
- session: {
345
- id: sessionId,
346
- agentType,
347
- agentSessionId: event.agentSessionId,
348
- },
349
- },
350
- });
351
- }
352
- if (event.state === 'delta') {
353
- const text = event.text ?? '';
354
- if (!text)
355
- return;
356
- const thinking = Boolean(event.thinking);
357
- if (activeSession?.pendingTextEvent &&
358
- (activeSession.pendingTextEvent.runId !== event.runId ||
359
- activeSession.pendingTextEvent.thinking !== thinking)) {
360
- flushTextBuffer(activeSession);
361
- }
362
- if (activeSession && !activeSession.pendingTextEvent) {
363
- activeSession.pendingTextEvent = { runId: event.runId, seq: event.seq, text: '', thinking };
364
- }
365
- if (activeSession?.pendingTextEvent) {
366
- activeSession.pendingTextEvent.text += text;
367
- activeSession.pendingTextEvent.seq = event.seq;
368
- }
369
- return;
370
- }
371
- let extra = {};
372
- if (event.state === 'final') {
373
- flushTextBuffer(activeSession);
374
- const accumulated = runtime.runTextAcc.get(runKey) ?? '';
375
- if (accumulated)
376
- extra = { messageSummary: extractSummary(accumulated) };
377
- runtime.runTextAcc.delete(runKey);
378
- }
379
- else if (event.state === 'error' || event.state === 'aborted') {
380
- flushTextBuffer(activeSession);
381
- runtime.runTextAcc.delete(runKey);
382
- }
383
- else if (event.state === 'tool-call' || event.state === 'tool-result' || event.state === 'approval-pending') {
384
- flushTextBuffer(activeSession);
385
- }
386
- if (isTerminalEvent &&
387
- activeSession?.currentRunId === event.runId) {
388
- activeSession.currentRunId = null;
389
- activeSession.currentRunPhase = null;
390
- activeSession.nextEventSeq = 0;
391
- stopActivityHeartbeat(activeSession);
392
- runtime.chatQueue?.noteTerminal(sessionId);
393
- }
394
- sendAgentEvent(event, extra);
395
- });
396
- adapter.on('error', (error) => {
397
- console.error(`[chat.send] adapter error sessionId=${sessionId} agentType=${agentType}: ${error.message}`);
398
- runtime.sessions.delete(sessionId);
399
- runtime.chatQueue?.noteTerminal(sessionId);
400
- runtime.client.sendEvent({
401
- type: 'event',
402
- event: 'agent',
403
- payload: { state: 'error', sessionId, message: error.message, runId: '', seq: 0 },
404
- });
405
- });
406
- }
407
- function rememberProcessedReqId(runtime, reqId) {
408
- runtime.processedReqIds.add(reqId);
409
- if (runtime.processedReqIds.size > 1000) {
410
- const first = runtime.processedReqIds.values().next().value;
411
- runtime.processedReqIds.delete(first);
412
- }
413
- }
414
- async function disposeSession(session) {
415
- if (session.heartbeatTimer) {
416
- clearInterval(session.heartbeatTimer);
417
- session.heartbeatTimer = null;
418
- }
419
- session.adapter.removeAllListeners();
420
- await session.adapter.stop().catch(() => { });
421
- }
422
- async function createActiveSession(runtime, sessionId, agentType, resolvedWorkDir, incomingAgentSid, externalChannel, externalReplyTarget) {
423
- runtime.evictIdleSessions();
424
- const adapter = createAgent(agentType);
425
- if (!adapter)
426
- throw new Error(`Unsupported agent: ${agentType}`);
427
- configureAdapterForSession(adapter, sessionId, agentType, externalChannel, externalReplyTarget);
428
- await adapter.start(sessionId, resolvedWorkDir, incomingAgentSid);
429
- const session = {
430
- adapter,
431
- workDir: resolvedWorkDir,
432
- agentType,
433
- agentSessionId: incomingAgentSid ?? null,
434
- lastActiveAt: Date.now(),
435
- currentRunId: null,
436
- currentRunPhase: null,
437
- nextEventSeq: 0,
438
- heartbeatSeq: 0,
439
- heartbeatTimer: null,
440
- pendingTextEvent: null,
441
- externalChannel: externalChannel ?? null,
442
- externalReplyTarget: externalReplyTarget ?? null,
443
- externalChannelEnv: {
444
- ...managedProviderEnv(agentType),
445
- ...externalChannelEnv(sessionId, externalChannel, externalReplyTarget),
446
- },
447
- };
448
- runtime.sessions.set(sessionId, session);
449
- bindAdapterEvents(runtime, sessionId, agentType, adapter);
450
- return session;
451
- }
452
- function emitSyntheticAbort(runtime, sessionId) {
453
- const session = runtime.sessions.get(sessionId);
454
- const runId = session?.currentRunId;
455
- if (!session || !runId)
456
- return;
457
- const seq = session.nextEventSeq;
458
- runtime.runTextAcc.delete(`${sessionId}:${runId}`);
459
- session.pendingTextEvent = null;
460
- session.currentRunId = null;
461
- session.currentRunPhase = null;
462
- session.nextEventSeq = 0;
463
- if (session.heartbeatTimer) {
464
- clearInterval(session.heartbeatTimer);
465
- session.heartbeatTimer = null;
466
- }
467
- runtime.client.sendAgentEvent({
468
- type: 'event',
469
- event: 'agent',
470
- payload: { state: 'aborted', sessionId, runId, seq },
471
- seq,
472
- id: `agent-evt-${runId}-${seq}`,
473
- });
474
- runtime.chatQueue?.noteTerminal(sessionId);
475
- }
476
- export async function handleChatSend(runtime, req) {
477
- if (runtime.processedReqIds.has(req.id)) {
478
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true });
479
- return;
480
- }
481
- rememberProcessedReqId(runtime, req.id);
482
- const { sessionId, text, agentType, workDir, agentSessionId: incomingAgentSid, modelId, managerDefaultWorkerAgentType, managerDefaultWorkerModelId, reasoningEffort, clientMessageId, sessionListProjection, waitForDispatch, responseId, } = req.params;
483
- const replyId = responseId || req.id;
484
- mergeProjectedSessions(sessionListProjection);
485
- const incomingExternalChannel = normalizeExternalChannel(req.params.externalChannel);
486
- const incomingReplyTarget = typeof req.params.replyTarget === 'string'
487
- ? req.params.replyTarget.trim()
488
- : '';
489
- if (!sessionId || !text) {
490
- runtime.processedReqIds.delete(req.id);
491
- runtime.client.sendRes({ type: 'res', id: replyId, ok: false, error: 'sessionId and text are required' });
492
- return;
493
- }
494
- const requestedAgentType = agentType;
495
- if (requestedAgentType === 'manager') {
496
- runtime.managerRuntime?.setManagerWorkerDefaults(sessionId, managerDefaultWorkerAgentType ?? null, managerDefaultWorkerModelId ?? null);
497
- }
498
- const resolvedReasoningEffort = (requestedAgentType === 'claude' || requestedAgentType === 'codex') &&
499
- typeof reasoningEffort === 'string' &&
500
- reasoningEffort.trim()
501
- ? reasoningEffort.trim()
502
- : undefined;
503
- const resolvedWorkDir = runtime.resolvePath(maybeResolveClaudeImportedWorkDir(requestedAgentType, workDir || os.homedir(), incomingAgentSid) || os.homedir());
504
- const displayWorkDir = resolvedWorkDir;
505
- const normalizedAttachments = normalizeChatAttachments(req.params.attachments);
506
- const materializedAttachments = normalizedAttachments?.length
507
- ? await materializeRemoteChatAttachments({ text, attachments: normalizedAttachments, workDir: resolvedWorkDir })
508
- : { text, attachments: normalizedAttachments, localized: false };
509
- let session = runtime.sessions.get(sessionId);
510
- if (session) {
511
- session.lastActiveAt = Date.now();
512
- const sessionDrifted = session.agentType !== requestedAgentType ||
513
- session.workDir !== resolvedWorkDir ||
514
- JSON.stringify(session.externalChannel ?? null) !== JSON.stringify(incomingExternalChannel ?? null);
515
- if (sessionDrifted) {
516
- runtime.sessions.delete(sessionId);
517
- try {
518
- await disposeSession(session);
519
- }
520
- catch {
521
- runtime.processedReqIds.delete(req.id);
522
- }
523
- session = undefined;
524
- }
525
- else if (incomingAgentSid && session.agentSessionId !== incomingAgentSid) {
526
- try {
527
- await session.adapter.resume(incomingAgentSid);
528
- session.agentSessionId = incomingAgentSid;
529
- }
530
- catch {
531
- runtime.sessions.delete(sessionId);
532
- try {
533
- await disposeSession(session);
534
- }
535
- catch {
536
- runtime.processedReqIds.delete(req.id);
537
- }
538
- session = undefined;
539
- }
540
- }
541
- }
542
- if (!session) {
543
- try {
544
- session = await createActiveSession(runtime, sessionId, requestedAgentType, resolvedWorkDir, incomingAgentSid, incomingExternalChannel, incomingReplyTarget);
545
- }
546
- catch (err) {
547
- const message = err instanceof Error && err.message.startsWith('Unsupported agent:')
548
- ? err.message
549
- : `Failed to start ${agentType}: ${err instanceof Error ? err.message : String(err)}`;
550
- console.error(`[chat.send] start failed reqId=${req.id} sessionId=${sessionId} agentType=${agentType} workDir=${resolvedWorkDir} agentSessionId=${incomingAgentSid ?? ''}: ${message}`);
551
- runtime.client.sendEvent({
552
- type: 'event',
553
- event: 'agent',
554
- payload: {
555
- state: 'error',
556
- sessionId,
557
- message,
558
- runId: '',
559
- seq: 0,
560
- },
561
- });
562
- runtime.processedReqIds.delete(req.id);
563
- runtime.client.sendRes({
564
- type: 'res',
565
- id: replyId,
566
- ok: false,
567
- error: message,
568
- });
569
- return;
570
- }
571
- }
572
- const userEnvelope = {
573
- id: clientMessageId ?? `user-${req.id}`,
574
- sessionId,
575
- role: 'user',
576
- ts: Date.now(),
577
- payload: buildUserMessagePayload(materializedAttachments.text, materializedAttachments.attachments),
578
- };
579
- reportLog({
580
- level: 'info',
581
- sessionId,
582
- wsEvent: 'chat.send.start',
583
- metadata: { reqId: req.id, agentType: requestedAgentType, modelId, reasoningEffort: resolvedReasoningEffort },
584
- });
585
- const markAccepted = () => {
586
- sendSessionUpdateEvent(runtime, {
587
- sessionId,
588
- agentType: requestedAgentType,
589
- workDir: displayWorkDir,
590
- agentSessionId: session.agentSessionId ?? incomingAgentSid ?? null,
591
- modelId,
592
- });
593
- // Some adapters (notably Codex app-server) emit `start` synchronously
594
- // during `send()` before chat.send has acknowledged dispatch. Do not wipe
595
- // that live run state here, or later chat.enqueue calls will think the
596
- // session is idle and bypass the daemon queue.
597
- if (!session.currentRunId) {
598
- session.nextEventSeq = 0;
599
- }
600
- runtime.nativeFusion?.registerManagedSend({
601
- sessionId,
602
- agentType: requestedAgentType,
603
- sourceAgentType: getNativeSourceAgentType(requestedAgentType, modelId),
604
- canonicalMessageId: clientMessageId ?? null,
605
- sourceSessionKey: session.agentSessionId ?? incomingAgentSid ?? null,
606
- text: materializedAttachments.text,
607
- });
608
- appendMessage(sessionId, userEnvelope);
609
- sendSessionMessageEvent(runtime, userEnvelope, {
610
- agentType: requestedAgentType,
611
- workDir: displayWorkDir,
612
- agentSessionId: session.agentSessionId ?? incomingAgentSid ?? null,
613
- modelId,
614
- });
615
- };
616
- const handleSendFailure = async (err, respondToReq) => {
617
- const message = formatAgentSendFailure(requestedAgentType, err);
618
- console.error(`[chat.send] send failed reqId=${req.id} sessionId=${sessionId} agentType=${agentType} workDir=${resolvedWorkDir} agentSessionId=${session.agentSessionId ?? incomingAgentSid ?? ''}: ${message}`);
619
- runtime.sessions.delete(sessionId);
620
- try {
621
- await disposeSession(session);
622
- }
623
- catch { /* best-effort cleanup */ }
624
- runtime.client.sendEvent({
625
- type: 'event',
626
- event: 'agent',
627
- payload: {
628
- state: 'error',
629
- sessionId,
630
- message,
631
- runId: '',
632
- seq: 0,
633
- },
634
- });
635
- if (!respondToReq) {
636
- const errorEnvelope = {
637
- id: `agent-error-${req.id}-${Date.now()}`,
638
- sessionId,
639
- role: 'agent',
640
- ts: Date.now(),
641
- payload: message,
642
- };
643
- appendMessage(sessionId, errorEnvelope);
644
- sendSessionMessageEvent(runtime, errorEnvelope, {
645
- agentType: requestedAgentType,
646
- workDir: displayWorkDir,
647
- agentSessionId: session.agentSessionId ?? incomingAgentSid ?? null,
648
- modelId,
649
- });
650
- }
651
- if (respondToReq) {
652
- runtime.processedReqIds.delete(req.id);
653
- runtime.client.sendRes({
654
- type: 'res',
655
- id: replyId,
656
- ok: false,
657
- error: message,
658
- });
659
- }
660
- };
661
- if (waitForDispatch) {
662
- try {
663
- const attachments = materializedAttachments.attachments;
664
- if (attachments?.length)
665
- await session.adapter.send(materializedAttachments.text, modelId, resolvedReasoningEffort, attachments);
666
- else if (resolvedReasoningEffort)
667
- await session.adapter.send(materializedAttachments.text, modelId, resolvedReasoningEffort);
668
- else
669
- await session.adapter.send(materializedAttachments.text, modelId);
670
- reportLog({
671
- level: 'info',
672
- sessionId,
673
- wsEvent: 'chat.send.done',
674
- metadata: { reqId: req.id },
675
- });
676
- }
677
- catch (err) {
678
- await handleSendFailure(err, true);
679
- return;
680
- }
681
- markAccepted();
682
- runtime.client.sendRes({
683
- type: 'res',
684
- id: replyId,
685
- ok: true,
686
- ...(materializedAttachments.localized ? { payload: { localizedAttachments: true } } : {}),
687
- });
688
- reportLog({
689
- level: 'info',
690
- sessionId,
691
- wsEvent: 'chat.send.res',
692
- metadata: { reqId: req.id, ok: true },
693
- });
694
- return;
695
- }
696
- markAccepted();
697
- runtime.client.sendRes({
698
- type: 'res',
699
- id: replyId,
700
- ok: true,
701
- ...(materializedAttachments.localized ? { payload: { localizedAttachments: true } } : {}),
702
- });
703
- reportLog({
704
- level: 'info',
705
- sessionId,
706
- wsEvent: 'chat.send.res',
707
- metadata: { reqId: req.id, ok: true },
708
- });
709
- const asyncAttachments = materializedAttachments.attachments;
710
- const sendPromise = asyncAttachments?.length
711
- ? session.adapter.send(materializedAttachments.text, modelId, resolvedReasoningEffort, asyncAttachments)
712
- : resolvedReasoningEffort
713
- ? session.adapter.send(materializedAttachments.text, modelId, resolvedReasoningEffort)
714
- : session.adapter.send(materializedAttachments.text, modelId);
715
- void sendPromise
716
- .then(() => {
717
- reportLog({
718
- level: 'info',
719
- sessionId,
720
- wsEvent: 'chat.send.done',
721
- metadata: { reqId: req.id },
722
- });
723
- })
724
- .catch((err) => {
725
- void handleSendFailure(err, false);
726
- });
727
- }
728
- export async function handleChatAbort(runtime, req) {
729
- const { sessionId } = req.params;
730
- const session = runtime.sessions.get(sessionId);
731
- if (session) {
732
- try {
733
- await session.adapter.stop();
734
- }
735
- catch { /* best-effort: still emit synthetic abort below */ }
736
- emitSyntheticAbort(runtime, sessionId);
737
- runtime.activityPublisher?.publish(sessionId, null);
738
- }
739
- else {
740
- const params = req.params;
741
- if (params.agentType === 'codex' && params.agentSessionId && runtime.nativeFusion) {
742
- const ok = await runtime.nativeFusion.interruptCodexThread({
743
- threadId: params.agentSessionId,
744
- workDir: params.workDir,
745
- });
746
- if (ok)
747
- runtime.activityPublisher?.publish(sessionId, null);
748
- }
749
- }
750
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true });
751
- }
1
+ import F from"node:os";import{createAgent as H}from"../../agents/adapter.js";import{buildApprovalPendingPayload as O,buildUserMessagePayload as G}from"@shennian/wire";import{reportLog as x}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as U}from"../../native-fusion/parsers.js";import{appendMessage as A,recordSession as v}from"../store.js";import{mergeProjectedSessions as J}from"../projection.js";import{getManagerRuntimeService as Q}from"../../manager/runtime.js";import{buildManagedAgentEnv as K}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as V}from"../remote-attachments.js";function X(t){if(!Array.isArray(t))return;const e=t.map(a=>{if(!a||typeof a!="object")return null;const s=a,i=typeof s.path=="string"?s.path:"",f=typeof s.name=="string"?s.name:"",l=typeof s.mimeType=="string"?s.mimeType:"";if(!i||!f||!l)return null;const d=typeof s.previewData=="string"&&s.previewData.trim()?s.previewData.trim():void 0;return{path:i,name:f,mimeType:l,kind:l.startsWith("image/")?"image":"file",...d?{previewData:d}:{}}}).filter(a=>a!=null);return e.length?e:void 0}function Y(t){const e=t.indexOf(`
2
+ `),a=e>0?Math.min(e,80):Math.min(t.length,80);return t.slice(0,a)}function Z(t,e,a={}){if(t.state==="tool-call"||t.state==="tool-result"){const s={runId:t.runId,sourceSeq:t.seq};return{state:t.state,runId:t.runId,seq:t.seq,sessionId:e,detailRef:s,...t.name?{name:t.name}:{},...t.source?{source:t.source}:{},...t.agentSessionId?{agentSessionId:t.agentSessionId}:{},...a}}return{...t,sessionId:e,...a}}const ee=3e4;function te(t){return t.state==="heartbeat"?t.runPhase??null:t.state==="tool-call"||t.state==="tool-result"?"tool_running":t.state==="approval-pending"?"waiting_approval":t.state==="delta"?t.thinking?"thinking":"streaming_text":t.state==="init"||t.state==="start"?"thinking":null}function ne(t,e){const a=e instanceof Error?e.message:String(e);return t==="pi"&&(a.includes("429")||a.includes("daily_quota_exceeded")||a.includes("nian_quota_exceeded"))?a.includes("too quickly")||a.includes("per minute")?"Nian \u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002":"Nian \u4ECA\u65E5\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u6B21\u65E5\u81EA\u52A8\u6062\u590D\u3002":`Agent send failed: ${a}`}function L(t,e){return t!=="manager"?t:e==="claude"?"claude":"codex"}function z(t,e,a){t.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:e.sessionId,message:e,session:{id:e.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:j(t,e.sessionId,a.agentType)}}})}function ae(t,e){v({sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e.sessionId,agentType:e.agentType,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,workDir:e.workDir,status:"active",externalChannel:j(t,e.sessionId,e.agentType)}}})}function re(t){if(!t||typeof t!="object")return null;const e=t;return{configured:e.configured===void 0?void 0:!!e.configured,connected:!!e.connected,type:typeof e.type=="string"?e.type:null,channelId:typeof e.channelId=="string"?e.channelId:null,name:typeof e.name=="string"?e.name:null,canReply:e.canReply===void 0||e.canReply===null?null:!!e.canReply,systemPrompt:typeof e.systemPrompt=="string"?e.systemPrompt:null,wechatRpaSource:typeof e.wechatRpaSource=="string"?e.wechatRpaSource:null,wechatRpaGroups:Array.isArray(e.wechatRpaGroups)?e.wechatRpaGroups.map(a=>({name:String(a?.name||"").trim()})).filter(a=>a.name):null,pollIntervalMs:Number.isFinite(e.pollIntervalMs)?Number(e.pollIntervalMs):null,recentLimit:Number.isFinite(e.recentLimit)?Number(e.recentLimit):null,idleSeconds:Number.isFinite(e.idleSeconds)?Number(e.idleSeconds):null,forceForeground:e.forceForeground===void 0||e.forceForeground===null?null:!!e.forceForeground,noRestore:e.noRestore===void 0||e.noRestore===null?null:!!e.noRestore,downloadAttachments:e.downloadAttachments===void 0||e.downloadAttachments===null?null:!!e.downloadAttachments,downloadAttachmentsDir:typeof e.downloadAttachmentsDir=="string"?e.downloadAttachmentsDir:null}}function se(t){return!!(t?.configured??t?.connected)}function B(t){return K(t)}function W(t,e,a){return se(e)?{...Q()?.getInjectedEnv(t,null,process.cwd(),"external")??{},SHENNIAN_EXTERNAL_SESSION_ID:t,SHENNIAN_MANAGER_SESSION_ID:t,...a?.trim()?{SHENNIAN_EXTERNAL_REPLY_TARGET:a.trim()}:{}}:{}}function oe(t,e,a,s,i){t.configure?.({sessionId:e,externalChannel:s??null,env:{...B(a),...W(e,s,i)}})}function j(t,e,a){const s=t.sessions.get(e);return s?.externalChannel?s.externalChannel:a==="manager"?t.managerRuntime?.getExternalChannelStatus(e)??null:null}function ie(t,e,a){if(t!=="claude"||!a)return e;const s=e.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?U(a)??e:e}function le(t,e,a,s){let i=null;function f(n,r={}){t.client.sendAgentEvent({type:"event",event:"agent",payload:Z(n,e,r),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function l(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function d(n){if(!n.currentRunId||!n.currentRunPhase)return;const r=n.heartbeatSeq++;t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:n.currentRunId,seq:r,runPhase:n.currentRunPhase},seq:r,id:`agent-heartbeat-${n.currentRunId}-${r}-${Date.now()}`})}function I(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{d(n)},ee),n.heartbeatTimer.unref?.())}function S(n){const r=n?.pendingTextEvent;!n||!r||!r.text||(f({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const r=t.sessions.get(e),R=te(n),T=n.state==="final"||n.state==="error"||n.state==="aborted";r&&(r.nextEventSeq=n.seq+1,T||(r.currentRunId=n.runId),!T&&R&&(r.currentRunPhase=R,I(r)),n.agentSessionId&&(r.agentSessionId=n.agentSessionId)),t.managerRuntime?.noteAgentEvent(e,n),n.state!=="delta"&&x({level:"info",sessionId:e,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?A(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:O(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&A(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.message});const m=`${e}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&t.runTextAcc.set(m,(t.runTextAcc.get(m)??"")+n.text),n.agentSessionId&&n.agentSessionId!==i&&(i=n.agentSessionId,t.nativeFusion?.noteManagedSourceSession(e,L(a,n.source),n.agentSessionId),r&&v({sessionId:e,agentType:a,workDir:r.workDir,agentSessionId:n.agentSessionId}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const h=n.text??"";if(!h)return;const k=!!n.thinking;r?.pendingTextEvent&&(r.pendingTextEvent.runId!==n.runId||r.pendingTextEvent.thinking!==k)&&S(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:k}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=h,r.pendingTextEvent.seq=n.seq);return}let p={};if(n.state==="final"){S(r);const h=t.runTextAcc.get(m)??"";h&&(p={messageSummary:Y(h)}),t.runTextAcc.delete(m)}else n.state==="error"||n.state==="aborted"?(S(r),t.runTextAcc.delete(m)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&S(r);T&&r?.currentRunId===n.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,l(r),t.chatQueue?.noteTerminal(e)),f(n,p)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${e} agentType=${a}: ${n.message}`),t.sessions.delete(e),t.chatQueue?.noteTerminal(e),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:e,message:n.message,runId:"",seq:0}})})}function de(t,e){if(t.processedReqIds.add(e),t.processedReqIds.size>1e3){const a=t.processedReqIds.values().next().value;t.processedReqIds.delete(a)}}async function q(t){t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}async function ce(t,e,a,s,i,f,l){t.evictIdleSessions();const d=H(a);if(!d)throw new Error(`Unsupported agent: ${a}`);oe(d,e,a,f,l),await d.start(e,s,i);const I={adapter:d,workDir:s,agentType:a,agentSessionId:i??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:f??null,externalReplyTarget:l??null,externalChannelEnv:{...B(a),...W(e,f,l)}};return t.sessions.set(e,I),le(t,e,a,d),I}function ue(t,e){const a=t.sessions.get(e),s=a?.currentRunId;if(!a||!s)return;const i=a.nextEventSeq;t.runTextAcc.delete(`${e}:${s}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:e,runId:s,seq:i},seq:i,id:`agent-evt-${s}-${i}`}),t.chatQueue?.noteTerminal(e)}async function Ae(t,e){if(t.processedReqIds.has(e.id)){t.client.sendRes({type:"res",id:e.id,ok:!0});return}de(t,e.id);const{sessionId:a,text:s,agentType:i,workDir:f,agentSessionId:l,modelId:d,managerDefaultWorkerAgentType:I,managerDefaultWorkerModelId:S,reasoningEffort:n,clientMessageId:r,sessionListProjection:R,waitForDispatch:T,responseId:m}=e.params,p=m||e.id;J(R);const h=re(e.params.externalChannel),k=typeof e.params.replyTarget=="string"?e.params.replyTarget.trim():"";if(!a||!s){t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:p,ok:!1,error:"sessionId and text are required"});return}const u=i;u==="manager"&&t.managerRuntime?.setManagerWorkerDefaults(a,I??null,S??null);const y=(u==="claude"||u==="codex")&&typeof n=="string"&&n.trim()?n.trim():void 0,E=t.resolvePath(ie(u,f||F.homedir(),l)||F.homedir()),D=E,$=X(e.params.attachments),g=$?.length?await V({text:s,attachments:$,workDir:E}):{text:s,attachments:$,localized:!1};let o=t.sessions.get(a);if(o){if(o.lastActiveAt=Date.now(),o.agentType!==u||o.workDir!==E||JSON.stringify(o.externalChannel??null)!==JSON.stringify(h??null)){t.sessions.delete(a);try{await q(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}else if(l&&o.agentSessionId!==l)try{await o.adapter.resume(l),o.agentSessionId=l}catch{t.sessions.delete(a);try{await q(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}}if(!o)try{o=await ce(t,a,u,E,l,h,k)}catch(c){const w=c instanceof Error&&c.message.startsWith("Unsupported agent:")?c.message:`Failed to start ${i}: ${c instanceof Error?c.message:String(c)}`;console.error(`[chat.send] start failed reqId=${e.id} sessionId=${a} agentType=${i} workDir=${E} agentSessionId=${l??""}: ${w}`),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:w,runId:"",seq:0}}),t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:p,ok:!1,error:w});return}const N={id:r??`user-${e.id}`,sessionId:a,role:"user",ts:Date.now(),payload:G(g.text,g.attachments)};x({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:e.id,agentType:u,modelId:d,reasoningEffort:y}});const P=()=>{ae(t,{sessionId:a,agentType:u,workDir:D,agentSessionId:o.agentSessionId??l??null,modelId:d}),o.currentRunId||(o.nextEventSeq=0),t.nativeFusion?.registerManagedSend({sessionId:a,agentType:u,sourceAgentType:L(u,d),canonicalMessageId:r??null,sourceSessionKey:o.agentSessionId??l??null,text:g.text}),A(a,N),z(t,N,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??l??null,modelId:d})},_=async(c,w)=>{const b=ne(u,c);console.error(`[chat.send] send failed reqId=${e.id} sessionId=${a} agentType=${i} workDir=${E} agentSessionId=${o.agentSessionId??l??""}: ${b}`),t.sessions.delete(a);try{await q(o)}catch{}if(t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:b,runId:"",seq:0}}),!w){const M={id:`agent-error-${e.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:b};A(a,M),z(t,M,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??l??null,modelId:d})}w&&(t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:p,ok:!1,error:b}))};if(T){try{const c=g.attachments;c?.length?await o.adapter.send(g.text,d,y,c):y?await o.adapter.send(g.text,d,y):await o.adapter.send(g.text,d),x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}catch(c){await _(c,!0);return}P(),t.client.sendRes({type:"res",id:p,ok:!0,...g.localized?{payload:{localizedAttachments:!0}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});return}P(),t.client.sendRes({type:"res",id:p,ok:!0,...g.localized?{payload:{localizedAttachments:!0}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});const C=g.attachments;(C?.length?o.adapter.send(g.text,d,y,C):y?o.adapter.send(g.text,d,y):o.adapter.send(g.text,d)).then(()=>{x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}).catch(c=>{_(c,!1)})}async function Te(t,e){const{sessionId:a}=e.params,s=t.sessions.get(a);if(s){try{await s.adapter.stop()}catch{}ue(t,a),t.activityPublisher?.publish(a,null)}else{const i=e.params;i.agentType==="codex"&&i.agentSessionId&&t.nativeFusion&&await t.nativeFusion.interruptCodexThread({threadId:i.agentSessionId,workDir:i.workDir})&&t.activityPublisher?.publish(a,null)}t.client.sendRes({type:"res",id:e.id,ok:!0})}export{Te as handleChatAbort,Ae as handleChatSend};