shennian 0.2.88 → 0.2.90

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