shennian 0.3.86 → 0.4.0

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 (98) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/shennian.js +1 -1
  3. package/dist/publish-build-manifest.json +32 -27
  4. package/dist/src/agents/adapter.js +1 -19
  5. package/dist/src/agents/claude-managed-permissions.js +1 -248
  6. package/dist/src/agents/claude.js +8 -393
  7. package/dist/src/agents/codex-managed-permissions.js +1 -294
  8. package/dist/src/agents/codex-managed-room-tools.js +1 -249
  9. package/dist/src/agents/codex-utils.js +7 -200
  10. package/dist/src/agents/codex.d.ts +7 -1
  11. package/dist/src/agents/codex.js +17 -1352
  12. package/dist/src/agents/config-status.js +1 -226
  13. package/dist/src/agents/cursor-managed-permissions.js +2 -299
  14. package/dist/src/agents/cursor.js +1 -314
  15. package/dist/src/agents/custom.js +4 -418
  16. package/dist/src/agents/external-channel-instructions.js +11 -94
  17. package/dist/src/agents/gemini.js +1 -180
  18. package/dist/src/agents/manager.js +18 -250
  19. package/dist/src/agents/model-registry/cache.js +1 -37
  20. package/dist/src/agents/model-registry/discovery.js +2 -188
  21. package/dist/src/agents/model-registry/parsers.js +4 -521
  22. package/dist/src/agents/model-registry/runner.js +1 -30
  23. package/dist/src/agents/model-registry/service.js +1 -115
  24. package/dist/src/agents/model-registry/types.js +1 -8
  25. package/dist/src/agents/model-registry.js +1 -19
  26. package/dist/src/agents/native-capabilities.js +1 -237
  27. package/dist/src/agents/openclaw.js +2 -293
  28. package/dist/src/agents/opencode-managed-permissions.js +2 -187
  29. package/dist/src/agents/opencode.js +1 -298
  30. package/dist/src/agents/platform-instructions.js +17 -46
  31. package/dist/src/agents/runtime-compatibility.js +1 -44
  32. package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
  33. package/dist/src/agents/workbuddy.js +3 -444
  34. package/dist/src/commands/logout.d.ts +3 -0
  35. package/dist/src/commands/logout.js +1 -0
  36. package/dist/src/daemon-ipc/paths.js +1 -1
  37. package/dist/src/fs/boundary.js +1 -185
  38. package/dist/src/fs/handler.js +1 -130
  39. package/dist/src/fs/security.js +1 -32
  40. package/dist/src/fs/text-decoder.js +1 -110
  41. package/dist/src/index.js +3 -587
  42. package/dist/src/manager/prompt.js +29 -34
  43. package/dist/src/manager/registry.js +1 -317
  44. package/dist/src/manager/retired-external-channel-compat.js +1 -40
  45. package/dist/src/manager/runtime.js +23 -1514
  46. package/dist/src/native-fusion/codex-parser.d.ts +3 -1
  47. package/dist/src/native-fusion/codex-parser.js +4 -660
  48. package/dist/src/native-fusion/codex-user-input.js +5 -102
  49. package/dist/src/native-fusion/config.js +1 -5
  50. package/dist/src/native-fusion/file-watcher.js +1 -191
  51. package/dist/src/native-fusion/local-repository.js +2 -569
  52. package/dist/src/native-fusion/opencode-parser.js +3 -130
  53. package/dist/src/native-fusion/parser-common.d.ts +6 -1
  54. package/dist/src/native-fusion/parser-common.js +4 -4
  55. package/dist/src/native-fusion/parsers.d.ts +4 -2
  56. package/dist/src/native-fusion/parsers.js +3 -140
  57. package/dist/src/native-fusion/service.js +2 -739
  58. package/dist/src/native-fusion/session-header.js +2 -101
  59. package/dist/src/native-fusion/state.js +2 -104
  60. package/dist/src/native-fusion/types.js +1 -1
  61. package/dist/src/personal-sync/machine-authority.js +1 -34
  62. package/dist/src/room/activation-audit.js +2 -45
  63. package/dist/src/room/activation-policy.js +1 -30
  64. package/dist/src/room/activation-service.js +8 -408
  65. package/dist/src/room/invite-bootstrap.js +1 -156
  66. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  67. package/dist/src/room/managed-prompt-composer.js +17 -16
  68. package/dist/src/room/managed-room-binding.js +1 -167
  69. package/dist/src/room/managed-room-ipc.d.ts +4 -0
  70. package/dist/src/room/managed-room-ipc.js +2 -2
  71. package/dist/src/room/mirror-sync-service.d.ts +1 -0
  72. package/dist/src/room/mirror-sync-service.js +1 -183
  73. package/dist/src/room/room-binding-client.js +1 -151
  74. package/dist/src/session/archive-zip.js +1 -220
  75. package/dist/src/session/deletion-sync-state.js +2 -136
  76. package/dist/src/session/handlers/agent-capabilities.js +1 -14
  77. package/dist/src/session/handlers/agent-config.js +1 -150
  78. package/dist/src/session/handlers/agents.js +1 -55
  79. package/dist/src/session/handlers/chat.js +2 -1030
  80. package/dist/src/session/handlers/control.js +1 -61
  81. package/dist/src/session/handlers/fs.js +1 -594
  82. package/dist/src/session/handlers/message-status.js +1 -15
  83. package/dist/src/session/handlers/remote-access.js +1 -43
  84. package/dist/src/session/handlers/room-managed.js +1 -101
  85. package/dist/src/session/handlers/session-refresh.js +1 -35
  86. package/dist/src/session/handlers/title.js +1 -79
  87. package/dist/src/session/handlers/tool-detail.js +1 -149
  88. package/dist/src/session/handlers/voice-window.js +1 -62
  89. package/dist/src/session/history-subscriptions.js +1 -470
  90. package/dist/src/session/manager.d.ts +1 -0
  91. package/dist/src/session/manager.js +1 -658
  92. package/dist/src/session/native-cleanup-outbox.js +2 -287
  93. package/dist/src/session/native-session-lifecycle.js +6 -327
  94. package/dist/src/session/queue.d.ts +8 -0
  95. package/dist/src/session/queue.js +6 -6
  96. package/dist/src/upgrade/scheduler.js +1 -126
  97. package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
  98. package/package.json +1 -1
@@ -1,1030 +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 { createHash } from 'node:crypto';
5
- import { createAgent } from '../../agents/adapter.js';
6
- import { buildApprovalPendingPayload, buildUserMessagePayload, } from '@shennian/wire';
7
- import { ManagerModeAdapter } from '../../agents/manager.js';
8
- import { reportLog } from '../../log-reporter.js';
9
- import { lookupClaudeTranscriptCwd } from '../../native-fusion/parsers.js';
10
- import { appendMessage, getDaemonInstallationId, getMessageDeliveryStatus, recordSession, upsertMessage, } from '../store.js';
11
- import { mergeProjectedSessions } from '../projection.js';
12
- import { buildManagedAgentEnv } from '../../agents/config-status.js';
13
- import { parsePersonalAttachmentReferences } from '../attachment-reference.js';
14
- import { assertFinishedAttachmentReferences, markAttachmentReferences, } from '../attachment-transfer-store.js';
15
- import { requestSessionIndexPublish } from '../index-publisher.js';
16
- import { writeToolDetail } from '../tool-detail-store.js';
17
- import { getPersonalSyncIdentity } from '../../personal-sync/epoch.js';
18
- import { buildManagedAgentPermissionPrompt } from '../../room/managed-prompt-composer.js';
19
- import { getManagedRoomIpcRoot, MANAGED_ROOM_IPC_ROOT_ENV } from '../../daemon-ipc/paths.js';
20
- function validateManagedRoomExecutionContext(context, systemPrompt, managedRoomContextToken) {
21
- if (!context.managedAgentPolicy)
22
- throw new Error('managed_agent_policy_missing');
23
- const snapshot = context.managedRoomPromptSnapshot;
24
- if (!snapshot)
25
- throw new Error('managed_room_prompt_snapshot_missing');
26
- if (!systemPrompt?.trim())
27
- throw new Error('managed_room_prompt_incomplete');
28
- if (!managedRoomContextToken || !/^mrc_[A-Za-z0-9_-]{43}$/.test(managedRoomContextToken)) {
29
- throw new Error('managed_room_context_unavailable');
30
- }
31
- const policyHash = createHash('sha256')
32
- .update(JSON.stringify({
33
- requestedPolicy: context.managedAgentPolicy.requestedPolicy,
34
- enforcement: context.managedAgentPolicy.enforcement,
35
- }))
36
- .digest('hex');
37
- const workDirHash = createHash('sha256')
38
- .update(context.managedAgentPolicy.adapterPolicy.workDir)
39
- .digest('hex');
40
- if (snapshot.runtimePermissionPolicyHash !== policyHash ||
41
- snapshot.resolvedWorkDirHash !== workDirHash ||
42
- snapshot.enforcementLevel !== context.managedAgentPolicy.enforcement.level) {
43
- throw new Error('managed_room_prompt_snapshot_mismatch');
44
- }
45
- }
46
- function extractSummary(text) {
47
- const newline = text.indexOf('\n');
48
- const end = newline > 0 ? Math.min(newline, 80) : Math.min(text.length, 80);
49
- return text.slice(0, end);
50
- }
51
- function stableAttentionId(kind, runId, seq) {
52
- return `${kind}:${createHash('sha256')
53
- .update(`${runId}\0${seq ?? ''}`)
54
- .digest('hex')}`;
55
- }
56
- function buildRelayAgentPayload(event, sessionId, extra = {}) {
57
- if (event.state === 'tool-call' || event.state === 'tool-result') {
58
- const detailRef = { runId: event.runId, sourceSeq: event.seq };
59
- return {
60
- state: event.state,
61
- runId: event.runId,
62
- seq: event.seq,
63
- sessionId,
64
- detailRef,
65
- status: event.state === 'tool-call' ? 'running' : 'completed',
66
- detailAvailable: true,
67
- ...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
68
- ...(event.name ? { name: event.name } : {}),
69
- ...(event.source ? { source: event.source } : {}),
70
- ...(event.agentSessionId ? { agentSessionId: event.agentSessionId } : {}),
71
- ...extra,
72
- };
73
- }
74
- return { ...event, sessionId, ...extra };
75
- }
76
- const SESSION_ACTIVITY_HEARTBEAT_INTERVAL_MS = 30_000;
77
- function runPhaseFromAgentEvent(event) {
78
- if (event.state === 'heartbeat')
79
- return event.runPhase ?? null;
80
- if (event.state === 'tool-call' || event.state === 'tool-result')
81
- return 'tool_running';
82
- if (event.state === 'approval-pending')
83
- return 'waiting_approval';
84
- if (event.state === 'delta')
85
- return event.thinking ? 'thinking' : 'streaming_text';
86
- if (event.state === 'init' || event.state === 'start')
87
- return 'thinking';
88
- return null;
89
- }
90
- function formatAgentSendFailure(agentType, err) {
91
- const raw = err instanceof Error ? err.message : String(err);
92
- return `Agent send failed: ${raw}`;
93
- }
94
- function getNativeSourceAgentType(agentType, modelId) {
95
- if (agentType !== 'manager')
96
- return agentType;
97
- return modelId === 'claude' ? 'claude' : 'codex';
98
- }
99
- export function sendSessionUpdateEvent(runtime, input) {
100
- recordSession({
101
- sessionId: input.sessionId,
102
- agentType: input.agentType,
103
- sessionMode: input.sessionMode,
104
- managerConfig: input.managerConfig,
105
- workDir: input.workDir,
106
- agentSessionId: input.agentSessionId ?? null,
107
- modelId: input.modelId ?? null,
108
- managerDefaultWorkerAgentType: input.managerDefaultWorkerAgentType,
109
- managerDefaultWorkerModelId: input.managerDefaultWorkerModelId,
110
- });
111
- runtime.client.sendEvent({
112
- type: 'event',
113
- event: 'session.update',
114
- payload: {
115
- session: {
116
- id: input.sessionId,
117
- agentType: input.agentType,
118
- sessionMode: input.sessionMode,
119
- managerConfig: input.managerConfig,
120
- agentSessionId: input.agentSessionId ?? null,
121
- modelId: input.modelId ?? null,
122
- workDir: input.workDir,
123
- status: 'active',
124
- externalChannel: null,
125
- },
126
- },
127
- });
128
- }
129
- function managedProviderEnv(agentType) {
130
- return buildManagedAgentEnv(agentType);
131
- }
132
- function externalChannelEnv(_sessionId, _channel, _replyTarget) {
133
- return {};
134
- }
135
- function configureAdapterForSession(adapter, sessionId, agentType, channel, replyTarget, managedRoomContextToken, managedAgentPolicy, managedRoomExecutor) {
136
- const useCodexDynamicTools = agentType === 'codex';
137
- adapter.configure?.({
138
- sessionId,
139
- externalChannel: channel ?? null,
140
- env: {
141
- ...managedProviderEnv(agentType),
142
- ...externalChannelEnv(sessionId, channel, replyTarget),
143
- ...(managedRoomContextToken && !useCodexDynamicTools
144
- ? {
145
- SHENNIAN_MANAGED_ROOM_CONTEXT: managedRoomContextToken,
146
- [MANAGED_ROOM_IPC_ROOT_ENV]: getManagedRoomIpcRoot(),
147
- }
148
- : {}),
149
- },
150
- ...(managedAgentPolicy ? { managedAgentPolicy } : {}),
151
- ...(useCodexDynamicTools
152
- ? {
153
- managedRoomContextToken: managedRoomContextToken ?? null,
154
- managedRoomExecutor: managedRoomExecutor ?? null,
155
- }
156
- : {}),
157
- });
158
- }
159
- function replaceManagedRoomContextToken(runtime, session, token) {
160
- const previous = session.managedRoomContextToken;
161
- if (previous && previous !== token)
162
- runtime.revokeManagedRoomContextToken?.(previous);
163
- session.managedRoomContextToken = token;
164
- }
165
- function revokeManagedRoomContextToken(runtime, session) {
166
- const token = session?.managedRoomContextToken;
167
- if (!session || !token)
168
- return;
169
- session.managedRoomContextToken = null;
170
- runtime.revokeManagedRoomContextToken?.(token);
171
- }
172
- function maybeResolveClaudeImportedWorkDir(agentType, workDir, agentSessionId) {
173
- if (agentType !== 'claude')
174
- return workDir;
175
- if (!agentSessionId)
176
- return workDir;
177
- const trimmed = workDir.trim();
178
- const looksLikeTranscriptSlug = !!trimmed && trimmed.startsWith('-') && !trimmed.includes('/');
179
- if (!looksLikeTranscriptSlug)
180
- return workDir;
181
- return lookupClaudeTranscriptCwd(agentSessionId) ?? workDir;
182
- }
183
- function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
184
- let emittedAgentSessionId = null;
185
- function sendAgentEvent(event, extra = {}) {
186
- runtime.client.sendAgentEvent({
187
- type: 'event',
188
- event: 'agent',
189
- payload: buildRelayAgentPayload(event, sessionId, extra),
190
- seq: event.seq,
191
- id: `agent-evt-${event.runId}-${event.seq}`,
192
- });
193
- }
194
- function stopActivityHeartbeat(activeSession) {
195
- if (!activeSession?.heartbeatTimer)
196
- return;
197
- clearInterval(activeSession.heartbeatTimer);
198
- activeSession.heartbeatTimer = null;
199
- }
200
- function sendActivityHeartbeat(activeSession) {
201
- if (!activeSession.currentRunId || !activeSession.currentRunPhase)
202
- return;
203
- const seq = activeSession.heartbeatSeq++;
204
- runtime.client.sendAgentEvent({
205
- type: 'event',
206
- event: 'agent',
207
- payload: {
208
- state: 'heartbeat',
209
- sessionId,
210
- runId: activeSession.currentRunId,
211
- seq,
212
- runPhase: activeSession.currentRunPhase,
213
- },
214
- seq,
215
- id: `agent-heartbeat-${activeSession.currentRunId}-${seq}-${Date.now()}`,
216
- });
217
- }
218
- function ensureActivityHeartbeat(activeSession) {
219
- if (!activeSession || activeSession.heartbeatTimer)
220
- return;
221
- activeSession.heartbeatTimer = setInterval(() => {
222
- sendActivityHeartbeat(activeSession);
223
- }, SESSION_ACTIVITY_HEARTBEAT_INTERVAL_MS);
224
- activeSession.heartbeatTimer.unref?.();
225
- }
226
- function flushTextBuffer(activeSession) {
227
- const textBuffer = activeSession?.pendingTextEvent;
228
- if (!activeSession || !textBuffer || !textBuffer.text)
229
- return;
230
- appendMessage(sessionId, {
231
- id: `agent-${textBuffer.runId}-${textBuffer.seq}`,
232
- sessionId,
233
- role: 'agent',
234
- ts: Date.now(),
235
- payload: textBuffer.text,
236
- });
237
- requestSessionIndexPublish(runtime.client);
238
- sendAgentEvent({
239
- state: 'delta',
240
- runId: textBuffer.runId,
241
- seq: textBuffer.seq,
242
- text: textBuffer.text,
243
- thinking: textBuffer.thinking || undefined,
244
- });
245
- activeSession.pendingTextEvent = null;
246
- }
247
- adapter.on('agentEvent', (event) => {
248
- const activeSession = runtime.sessions.get(sessionId);
249
- const runPhase = runPhaseFromAgentEvent(event);
250
- const isTerminalEvent = event.state === 'final' || event.state === 'error' || event.state === 'aborted';
251
- if (activeSession) {
252
- activeSession.nextEventSeq = event.seq + 1;
253
- if (!isTerminalEvent)
254
- activeSession.currentRunId = event.runId;
255
- if (!isTerminalEvent && runPhase) {
256
- activeSession.currentRunPhase = runPhase;
257
- ensureActivityHeartbeat(activeSession);
258
- }
259
- if (event.agentSessionId)
260
- activeSession.agentSessionId = event.agentSessionId;
261
- }
262
- runtime.managerRuntime?.noteAgentEvent(sessionId, event);
263
- // Reasoning is an implementation detail of the local Agent. Keep it on the
264
- // machine: it must not enter Relay persistence or become a chat message.
265
- if (event.state === 'delta' && event.thinking)
266
- return;
267
- if (event.state !== 'delta') {
268
- reportLog({
269
- level: 'info',
270
- sessionId,
271
- wsEvent: `agent.${event.state}`,
272
- wsDirection: 'out',
273
- metadata: { runId: event.runId, seq: event.seq, agentType },
274
- });
275
- }
276
- let committedVisibleEvent = false;
277
- if (event.state === 'tool-call' || event.state === 'tool-result') {
278
- const toolMessageId = `agent-${event.runId}-${event.seq}`;
279
- writeToolDetail({
280
- sessionId,
281
- toolCallId: toolMessageId,
282
- detailRevision: event.seq,
283
- ts: Date.now(),
284
- name: event.name ?? 'tool',
285
- ...(event.args !== undefined ? { args: event.args } : {}),
286
- ...(event.result !== undefined ? { result: event.result } : {}),
287
- status: event.state === 'tool-call' ? 'running' : 'completed',
288
- ...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
289
- detailRef: { runId: event.runId, sourceSeq: event.seq },
290
- });
291
- appendMessage(sessionId, {
292
- id: toolMessageId,
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
- toolCallId: toolMessageId,
301
- status: event.state === 'tool-call' ? 'running' : 'completed',
302
- ...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
303
- detailRef: { runId: event.runId, sourceSeq: event.seq },
304
- detailRevision: event.seq,
305
- detailAvailable: true,
306
- resultOmitted: event.state === 'tool-result',
307
- }),
308
- });
309
- committedVisibleEvent = true;
310
- }
311
- else if (event.state === 'approval-pending') {
312
- appendMessage(sessionId, {
313
- id: `agent-${event.runId || 'run'}-${event.seq}`,
314
- sessionId,
315
- role: 'agent',
316
- ts: Date.now(),
317
- payload: buildApprovalPendingPayload(event.approval),
318
- });
319
- committedVisibleEvent = true;
320
- if (activeSession) {
321
- recordSession({
322
- sessionId,
323
- agentType,
324
- workDir: activeSession.workDir,
325
- agentSessionId: activeSession.agentSessionId,
326
- attentionId: stableAttentionId('approval', event.runId, event.seq),
327
- });
328
- }
329
- }
330
- else if ((event.state === 'error' || event.state === 'aborted') && event.message) {
331
- appendMessage(sessionId, {
332
- id: `agent-${event.runId || 'run'}-${event.seq}`,
333
- sessionId,
334
- role: 'agent',
335
- ts: Date.now(),
336
- payload: event.message,
337
- });
338
- committedVisibleEvent = true;
339
- }
340
- if (committedVisibleEvent)
341
- requestSessionIndexPublish(runtime.client);
342
- const runKey = `${sessionId}:${event.runId}`;
343
- if (event.state === 'delta' && !event.thinking && event.text) {
344
- runtime.runTextAcc.set(runKey, (runtime.runTextAcc.get(runKey) ?? '') + event.text);
345
- }
346
- if (event.agentSessionId && event.agentSessionId !== emittedAgentSessionId) {
347
- emittedAgentSessionId = event.agentSessionId;
348
- runtime.nativeFusion?.noteManagedSourceSession(sessionId, getNativeSourceAgentType(agentType, event.source), event.agentSessionId);
349
- if (activeSession) {
350
- recordSession({
351
- sessionId,
352
- agentType,
353
- workDir: activeSession.workDir,
354
- agentSessionId: event.agentSessionId,
355
- });
356
- }
357
- runtime.client.sendEvent({
358
- type: 'event',
359
- event: 'session.update',
360
- payload: {
361
- session: {
362
- id: sessionId,
363
- agentType,
364
- agentSessionId: event.agentSessionId,
365
- },
366
- },
367
- });
368
- }
369
- if (event.state === 'delta') {
370
- const text = event.text ?? '';
371
- if (!text)
372
- return;
373
- if (activeSession?.pendingTextEvent && activeSession.pendingTextEvent.runId !== event.runId) {
374
- flushTextBuffer(activeSession);
375
- }
376
- if (activeSession && !activeSession.pendingTextEvent) {
377
- activeSession.pendingTextEvent = {
378
- runId: event.runId,
379
- seq: event.seq,
380
- text: '',
381
- thinking: false,
382
- };
383
- }
384
- if (activeSession?.pendingTextEvent) {
385
- activeSession.pendingTextEvent.text += text;
386
- activeSession.pendingTextEvent.seq = event.seq;
387
- }
388
- return;
389
- }
390
- let extra = {};
391
- if (event.state === 'final') {
392
- flushTextBuffer(activeSession);
393
- const accumulated = runtime.runTextAcc.get(runKey) ?? '';
394
- if (accumulated)
395
- extra = { messageSummary: extractSummary(accumulated) };
396
- runtime.runTextAcc.delete(runKey);
397
- }
398
- else if (event.state === 'error' || event.state === 'aborted') {
399
- flushTextBuffer(activeSession);
400
- runtime.runTextAcc.delete(runKey);
401
- }
402
- else if (event.state === 'tool-call' ||
403
- event.state === 'tool-result' ||
404
- event.state === 'approval-pending') {
405
- flushTextBuffer(activeSession);
406
- }
407
- if (isTerminalEvent && activeSession?.currentRunId === event.runId) {
408
- revokeManagedRoomContextToken(runtime, activeSession);
409
- activeSession.currentRunId = null;
410
- activeSession.currentRunPhase = null;
411
- activeSession.nextEventSeq = 0;
412
- stopActivityHeartbeat(activeSession);
413
- runtime.chatQueue?.noteTerminal(sessionId);
414
- recordSession({
415
- sessionId,
416
- agentType,
417
- workDir: activeSession.workDir,
418
- agentSessionId: activeSession.agentSessionId,
419
- status: event.state === 'final' ? 'completed' : 'failed',
420
- attentionId: stableAttentionId('terminal', event.runId),
421
- lastActivityAt: new Date().toISOString(),
422
- });
423
- requestSessionIndexPublish(runtime.client);
424
- }
425
- sendAgentEvent(event, extra);
426
- });
427
- adapter.on('error', (error) => {
428
- console.error(`[chat.send] adapter error sessionId=${sessionId} agentType=${agentType}: ${error.message}`);
429
- revokeManagedRoomContextToken(runtime, runtime.sessions.get(sessionId));
430
- runtime.sessions.delete(sessionId);
431
- runtime.chatQueue?.noteTerminal(sessionId);
432
- runtime.client.sendEvent({
433
- type: 'event',
434
- event: 'agent',
435
- payload: { state: 'error', sessionId, message: error.message, runId: '', seq: 0 },
436
- });
437
- });
438
- }
439
- function rememberProcessedReqId(runtime, reqId) {
440
- runtime.processedReqIds.add(reqId);
441
- if (runtime.processedReqIds.size > 1000) {
442
- const first = runtime.processedReqIds.values().next().value;
443
- runtime.processedReqIds.delete(first);
444
- }
445
- }
446
- async function disposeSession(runtime, session) {
447
- revokeManagedRoomContextToken(runtime, session);
448
- if (session.heartbeatTimer) {
449
- clearInterval(session.heartbeatTimer);
450
- session.heartbeatTimer = null;
451
- }
452
- session.adapter.removeAllListeners();
453
- await session.adapter.stop().catch(() => { });
454
- }
455
- function sendAdapterTurn(adapter, text, modelId, reasoningEffort, attachments, systemPrompt, context) {
456
- if (context) {
457
- return adapter.send(text, modelId, reasoningEffort, attachments, systemPrompt, context);
458
- }
459
- if (systemPrompt?.trim()) {
460
- return adapter.send(text, modelId, reasoningEffort, attachments, systemPrompt);
461
- }
462
- if (attachments?.length)
463
- return adapter.send(text, modelId, reasoningEffort, attachments);
464
- if (reasoningEffort)
465
- return adapter.send(text, modelId, reasoningEffort);
466
- return adapter.send(text, modelId);
467
- }
468
- function prepareAdapterTurn(adapter, text, modelId, reasoningEffort, attachments, systemPrompt, context) {
469
- if (!adapter.prepareSend)
470
- return Promise.resolve();
471
- return adapter.prepareSend(text, modelId, reasoningEffort, attachments, systemPrompt, context);
472
- }
473
- async function createActiveSession(runtime, sessionId, agentType, sessionMode, resolvedWorkDir, incomingAgentSid, externalChannel, externalReplyTarget, managedAgentPolicy) {
474
- runtime.evictIdleSessions();
475
- const baseAdapter = createAgent(agentType);
476
- if (!baseAdapter)
477
- throw new Error(`Unsupported agent: ${agentType}`);
478
- const adapter = sessionMode === 'manager' && agentType !== 'manager'
479
- ? new ManagerModeAdapter(baseAdapter, agentType === 'claude' ? 'claude' : 'codex')
480
- : baseAdapter;
481
- configureAdapterForSession(adapter, sessionId, agentType, externalChannel, externalReplyTarget, null, managedAgentPolicy, runtime.managedRoomExecutor);
482
- await adapter.start(sessionId, resolvedWorkDir, incomingAgentSid);
483
- const session = {
484
- adapter,
485
- workDir: resolvedWorkDir,
486
- agentType,
487
- sessionMode,
488
- agentSessionId: incomingAgentSid ?? null,
489
- lastActiveAt: Date.now(),
490
- currentRunId: null,
491
- currentRunPhase: null,
492
- nextEventSeq: 0,
493
- heartbeatSeq: 0,
494
- heartbeatTimer: null,
495
- pendingTextEvent: null,
496
- externalChannel: externalChannel ?? null,
497
- externalReplyTarget: externalReplyTarget ?? null,
498
- externalChannelEnv: {
499
- ...managedProviderEnv(agentType),
500
- ...externalChannelEnv(sessionId, externalChannel, externalReplyTarget),
501
- },
502
- managedRoomContextToken: null,
503
- };
504
- runtime.sessions.set(sessionId, session);
505
- bindAdapterEvents(runtime, sessionId, agentType, adapter);
506
- return session;
507
- }
508
- function emitSyntheticAbort(runtime, sessionId) {
509
- const session = runtime.sessions.get(sessionId);
510
- const runId = session?.currentRunId;
511
- if (!session || !runId)
512
- return;
513
- const seq = session.nextEventSeq;
514
- runtime.runTextAcc.delete(`${sessionId}:${runId}`);
515
- revokeManagedRoomContextToken(runtime, session);
516
- session.pendingTextEvent = null;
517
- session.currentRunId = null;
518
- session.currentRunPhase = null;
519
- session.nextEventSeq = 0;
520
- if (session.heartbeatTimer) {
521
- clearInterval(session.heartbeatTimer);
522
- session.heartbeatTimer = null;
523
- }
524
- runtime.client.sendAgentEvent({
525
- type: 'event',
526
- event: 'agent',
527
- payload: { state: 'aborted', sessionId, runId, seq },
528
- seq,
529
- id: `agent-evt-${runId}-${seq}`,
530
- });
531
- runtime.chatQueue?.noteTerminal(sessionId);
532
- }
533
- export async function handleChatSend(runtime, req, context) {
534
- const managedRoomToken = context
535
- ? (req.params.managedRoomContextToken ?? null)
536
- : null;
537
- let transferredToTurn = false;
538
- try {
539
- await handleChatSendImpl(runtime, req, context, () => {
540
- transferredToTurn = true;
541
- });
542
- }
543
- finally {
544
- if (managedRoomToken && !transferredToTurn) {
545
- runtime.revokeManagedRoomContextToken?.(managedRoomToken);
546
- }
547
- }
548
- }
549
- async function handleChatSendImpl(runtime, req, context, transferManagedRoomTokenToTurn) {
550
- if (runtime.processedReqIds.has(req.id)) {
551
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true });
552
- return;
553
- }
554
- const { sessionId, text, agentType, sessionMode: incomingSessionMode, managerConfig, workDir, agentSessionId: incomingAgentSid, modelId, systemPrompt, managerDefaultWorkerAgentType, managerDefaultWorkerModelId, reasoningEffort, clientMessageId, sessionListProjection, waitForDispatch, responseId, managedRoomContextToken, suppressExternalOwnerEcho, queuedBatchItems, managedWorkerParentSessionId, } = req.params;
555
- const replyId = responseId || req.id;
556
- mergeProjectedSessions(sessionListProjection);
557
- const incomingExternalChannel = null;
558
- const incomingReplyTarget = '';
559
- if (context?.source === 'room_activation') {
560
- validateManagedRoomExecutionContext(context, systemPrompt, managedRoomContextToken);
561
- if (agentType === 'manager') {
562
- throw new Error('managed_agent_manager_mode_unsupported');
563
- }
564
- }
565
- rememberProcessedReqId(runtime, req.id);
566
- if (!sessionId || !text) {
567
- runtime.processedReqIds.delete(req.id);
568
- runtime.client.sendRes({
569
- type: 'res',
570
- id: replyId,
571
- ok: false,
572
- error: 'sessionId and text are required',
573
- });
574
- return;
575
- }
576
- const requestedAgentType = agentType;
577
- const requestedSessionMode = requestedAgentType === 'manager' || incomingSessionMode === 'manager' ? 'manager' : 'standard';
578
- if (requestedSessionMode === 'manager') {
579
- runtime.managerRuntime?.setManagerWorkerDefaults(sessionId, managerDefaultWorkerAgentType ?? null, managerDefaultWorkerModelId ?? null);
580
- }
581
- const resolvedReasoningEffort = (requestedAgentType === 'claude' ||
582
- requestedAgentType === 'codex' ||
583
- requestedAgentType === 'workbuddy') &&
584
- typeof reasoningEffort === 'string' &&
585
- reasoningEffort.trim()
586
- ? reasoningEffort.trim()
587
- : undefined;
588
- const resolvedWorkDir = runtime.resolvePath(maybeResolveClaudeImportedWorkDir(requestedAgentType, workDir || os.homedir(), incomingAgentSid) || os.homedir());
589
- const inheritedManagedAgentPolicy = context?.managedAgentPolicy ?? (managedWorkerParentSessionId
590
- ? runtime.managerRuntime?.getManagedAgentPolicyForWorker(managedWorkerParentSessionId, resolvedWorkDir) ?? null
591
- : null);
592
- if (managedWorkerParentSessionId && !inheritedManagedAgentPolicy) {
593
- throw new Error('managed_worker_permission_context_unavailable');
594
- }
595
- const resolvedSystemPrompt = managedWorkerParentSessionId && inheritedManagedAgentPolicy
596
- ? buildManagedAgentPermissionPrompt({
597
- requestedPolicy: inheritedManagedAgentPolicy.requestedPolicy,
598
- enforcement: inheritedManagedAgentPolicy.enforcement,
599
- resolvedWorkDir: inheritedManagedAgentPolicy.adapterPolicy.workDir,
600
- })
601
- : systemPrompt;
602
- const displayWorkDir = resolvedWorkDir;
603
- const normalizedAttachments = parsePersonalAttachmentReferences(req.params.attachments);
604
- if (normalizedAttachments?.length) {
605
- assertFinishedAttachmentReferences(resolvedWorkDir, normalizedAttachments);
606
- }
607
- const materializedAttachments = {
608
- text,
609
- attachments: normalizedAttachments,
610
- localized: false,
611
- };
612
- let session = runtime.sessions.get(sessionId);
613
- if (session) {
614
- session.lastActiveAt = Date.now();
615
- const sessionDrifted = session.agentType !== requestedAgentType ||
616
- (session.sessionMode ?? (session.agentType === 'manager' ? 'manager' : 'standard')) !==
617
- requestedSessionMode ||
618
- session.workDir !== resolvedWorkDir ||
619
- JSON.stringify(session.externalChannel ?? null) !==
620
- JSON.stringify(incomingExternalChannel ?? null);
621
- if (sessionDrifted) {
622
- runtime.sessions.delete(sessionId);
623
- try {
624
- await disposeSession(runtime, session);
625
- }
626
- catch {
627
- runtime.processedReqIds.delete(req.id);
628
- }
629
- session = undefined;
630
- }
631
- else if (incomingAgentSid && session.agentSessionId !== incomingAgentSid) {
632
- try {
633
- await session.adapter.resume(incomingAgentSid);
634
- session.agentSessionId = incomingAgentSid;
635
- }
636
- catch {
637
- runtime.sessions.delete(sessionId);
638
- try {
639
- await disposeSession(runtime, session);
640
- }
641
- catch {
642
- runtime.processedReqIds.delete(req.id);
643
- }
644
- session = undefined;
645
- }
646
- }
647
- }
648
- if (!session) {
649
- try {
650
- session = await createActiveSession(runtime, sessionId, requestedAgentType, requestedSessionMode, resolvedWorkDir, incomingAgentSid, incomingExternalChannel, incomingReplyTarget, inheritedManagedAgentPolicy);
651
- }
652
- catch (err) {
653
- const message = err instanceof Error && err.message.startsWith('Unsupported agent:')
654
- ? err.message
655
- : `Failed to start ${agentType}: ${err instanceof Error ? err.message : String(err)}`;
656
- console.error(`[chat.send] start failed reqId=${req.id} sessionId=${sessionId} agentType=${agentType} workDir=${resolvedWorkDir} agentSessionId=${incomingAgentSid ?? ''}: ${message}`);
657
- runtime.client.sendEvent({
658
- type: 'event',
659
- event: 'agent',
660
- payload: {
661
- state: 'error',
662
- sessionId,
663
- message,
664
- runId: '',
665
- seq: 0,
666
- },
667
- });
668
- runtime.processedReqIds.delete(req.id);
669
- runtime.client.sendRes({
670
- type: 'res',
671
- id: replyId,
672
- ok: false,
673
- error: message,
674
- });
675
- return;
676
- }
677
- }
678
- const userEnvelope = {
679
- id: clientMessageId ?? `user-${req.id}`,
680
- sessionId,
681
- role: 'user',
682
- ts: Date.now(),
683
- payload: buildUserMessagePayload(materializedAttachments.text, materializedAttachments.attachments),
684
- };
685
- const queuedBatchEnvelopes = Array.isArray(queuedBatchItems) && queuedBatchItems.length > 0
686
- ? queuedBatchItems.map((message) => ({
687
- id: message.clientMessageId ?? message.id,
688
- sessionId,
689
- role: 'user',
690
- ts: Date.parse(message.createdAt),
691
- payload: buildUserMessagePayload(message.text, message.attachments),
692
- }))
693
- : null;
694
- const committedUserEnvelopes = queuedBatchEnvelopes ?? [userEnvelope];
695
- const existingDelivery = getMessageDeliveryStatus(sessionId, userEnvelope.id);
696
- const resumingQueuedMessage = existingDelivery.state === 'queued';
697
- if (existingDelivery.state !== 'not_found' && !resumingQueuedMessage) {
698
- runtime.client.sendRes({ type: 'res', id: replyId, ok: true, payload: existingDelivery });
699
- return;
700
- }
701
- if (waitForDispatch) {
702
- if (context?.source === 'room_activation') {
703
- // Codex prepareSend may start app-server and create the managed thread.
704
- // Bind the per-turn Room tools before that preparation so the thread is
705
- // created with the fail-closed dynamic tool surface already available.
706
- configureAdapterForSession(session.adapter, sessionId, requestedAgentType, incomingExternalChannel, incomingReplyTarget, managedRoomContextToken, context.managedAgentPolicy, runtime.managedRoomExecutor);
707
- }
708
- try {
709
- await prepareAdapterTurn(session.adapter, materializedAttachments.text, modelId, resolvedReasoningEffort, materializedAttachments.attachments, resolvedSystemPrompt, context ? { source: context.source } : undefined);
710
- }
711
- catch (error) {
712
- runtime.processedReqIds.delete(req.id);
713
- runtime.sessions.delete(sessionId);
714
- await disposeSession(runtime, session);
715
- runtime.client.sendRes({
716
- type: 'res',
717
- id: replyId,
718
- ok: false,
719
- error: error instanceof Error ? error.message : String(error),
720
- });
721
- return;
722
- }
723
- }
724
- reportLog({
725
- level: 'info',
726
- sessionId,
727
- wsEvent: 'chat.send.start',
728
- metadata: {
729
- reqId: req.id,
730
- agentType: requestedAgentType,
731
- sessionMode: requestedSessionMode,
732
- managerConfig: managerConfig ?? null,
733
- modelId,
734
- managerDefaultWorkerAgentType: managerDefaultWorkerAgentType ?? null,
735
- managerDefaultWorkerModelId: managerDefaultWorkerModelId ?? null,
736
- reasoningEffort: resolvedReasoningEffort,
737
- ...(context?.source === 'room_activation'
738
- ? { managedRoomPromptSnapshot: context.managedRoomPromptSnapshot }
739
- : {}),
740
- },
741
- });
742
- const durableAck = (deliveryState, bodyRevision) => ({
743
- ...getPersonalSyncIdentity(getDaemonInstallationId()),
744
- sessionId,
745
- clientMessageId: userEnvelope.id,
746
- messageId: userEnvelope.id,
747
- deliveryState,
748
- bodyRevision,
749
- committedAt: new Date(userEnvelope.ts).toISOString(),
750
- });
751
- const registerManagedClaim = (sendResult) => {
752
- runtime.nativeFusion?.registerManagedSend({
753
- sessionId,
754
- agentType: requestedAgentType,
755
- sourceAgentType: getNativeSourceAgentType(requestedAgentType, modelId),
756
- canonicalMessageId: userEnvelope.id,
757
- sourceSessionKey: session.agentSessionId ?? incomingAgentSid ?? null,
758
- text: materializedAttachments.text,
759
- managedEchoPolicy: sendResult?.deliveryMode === 'external_owner_queue' && !suppressExternalOwnerEcho
760
- ? 'import_following'
761
- : 'suppress_following',
762
- externalRunId: sendResult?.deliveryMode === 'external_owner_queue'
763
- ? `codex-external-${sendResult.queuedSubmissionId ?? userEnvelope.id}`
764
- : null,
765
- });
766
- };
767
- const markDispatchPending = () => {
768
- if (session.currentRunId)
769
- return;
770
- session.currentRunId = `dispatch:${req.id}`;
771
- session.currentRunPhase = 'thinking';
772
- session.nextEventSeq = 0;
773
- };
774
- const commitUserBeforeDispatch = () => {
775
- registerManagedClaim();
776
- if (resumingQueuedMessage) {
777
- return {
778
- ...getPersonalSyncIdentity(getDaemonInstallationId()),
779
- sessionId,
780
- clientMessageId: existingDelivery.clientMessageId,
781
- messageId: existingDelivery.messageId,
782
- deliveryState: existingDelivery.deliveryState,
783
- bodyRevision: existingDelivery.bodyRevision,
784
- committedAt: existingDelivery.committedAt,
785
- };
786
- }
787
- try {
788
- sendSessionUpdateEvent(runtime, {
789
- sessionId,
790
- agentType: requestedAgentType,
791
- sessionMode: requestedSessionMode,
792
- managerConfig: managerConfig ?? null,
793
- workDir: displayWorkDir,
794
- agentSessionId: session.agentSessionId ?? incomingAgentSid ?? null,
795
- modelId,
796
- managerDefaultWorkerAgentType: managerDefaultWorkerAgentType ?? null,
797
- managerDefaultWorkerModelId: managerDefaultWorkerModelId ?? null,
798
- });
799
- let primaryBodyRevision = 0;
800
- for (const [index, envelope] of committedUserEnvelopes.entries()) {
801
- const bodyRevision = queuedBatchEnvelopes
802
- ? upsertMessage(sessionId, envelope, 'committed')
803
- : appendMessage(sessionId, envelope, 'committed');
804
- if (index === 0)
805
- primaryBodyRevision = bodyRevision;
806
- }
807
- markAttachmentReferences(resolvedWorkDir, normalizedAttachments ?? []);
808
- requestSessionIndexPublish(runtime.client);
809
- return durableAck('committed', primaryBodyRevision);
810
- }
811
- catch (error) {
812
- runtime.nativeFusion?.cancelManagedSend?.(userEnvelope.id);
813
- throw error;
814
- }
815
- };
816
- const markAccepted = (sendResult) => {
817
- // Some adapters (notably Codex app-server) emit `start` synchronously
818
- // during `send()` before chat.send has acknowledged dispatch. Do not wipe
819
- // that live run state here, or later chat.enqueue calls will think the
820
- // session is idle and bypass the daemon queue.
821
- if (!session.currentRunId) {
822
- session.nextEventSeq = 0;
823
- }
824
- registerManagedClaim(sendResult);
825
- const state = sendResult?.deliveryMode === 'external_owner_queue' ? 'queued' : 'executing';
826
- let primaryBodyRevision = 0;
827
- for (const [index, envelope] of committedUserEnvelopes.entries()) {
828
- const bodyRevision = upsertMessage(sessionId, envelope, state);
829
- if (index === 0)
830
- primaryBodyRevision = bodyRevision;
831
- }
832
- requestSessionIndexPublish(runtime.client);
833
- return durableAck(state, primaryBodyRevision);
834
- };
835
- const handleSendFailure = async (err, respondToReq) => {
836
- runtime.nativeFusion?.cancelManagedSend?.(userEnvelope.id);
837
- const message = formatAgentSendFailure(requestedAgentType, err);
838
- console.error(`[chat.send] send failed reqId=${req.id} sessionId=${sessionId} agentType=${agentType} workDir=${resolvedWorkDir} agentSessionId=${session.agentSessionId ?? incomingAgentSid ?? ''}: ${message}`);
839
- runtime.sessions.delete(sessionId);
840
- try {
841
- await disposeSession(runtime, session);
842
- }
843
- catch {
844
- /* best-effort cleanup */
845
- }
846
- runtime.client.sendEvent({
847
- type: 'event',
848
- event: 'agent',
849
- payload: {
850
- state: 'error',
851
- sessionId,
852
- message,
853
- runId: '',
854
- seq: 0,
855
- },
856
- });
857
- if (!respondToReq) {
858
- const errorEnvelope = {
859
- id: `agent-error-${req.id}-${Date.now()}`,
860
- sessionId,
861
- role: 'agent',
862
- ts: Date.now(),
863
- payload: message,
864
- };
865
- appendMessage(sessionId, errorEnvelope);
866
- requestSessionIndexPublish(runtime.client);
867
- }
868
- if (respondToReq) {
869
- runtime.processedReqIds.delete(req.id);
870
- runtime.client.sendRes({
871
- type: 'res',
872
- id: replyId,
873
- ok: false,
874
- error: message,
875
- });
876
- }
877
- };
878
- if (waitForDispatch) {
879
- try {
880
- commitUserBeforeDispatch();
881
- }
882
- catch (err) {
883
- runtime.processedReqIds.delete(req.id);
884
- runtime.client.sendRes({
885
- type: 'res',
886
- id: replyId,
887
- ok: false,
888
- error: `Durable message commit failed: ${err instanceof Error ? err.message : String(err)}`,
889
- });
890
- return;
891
- }
892
- markDispatchPending();
893
- let sendResult;
894
- try {
895
- if (context?.source === 'room_activation') {
896
- replaceManagedRoomContextToken(runtime, session, managedRoomContextToken);
897
- transferManagedRoomTokenToTurn();
898
- }
899
- const attachments = materializedAttachments.attachments;
900
- sendResult = await sendAdapterTurn(session.adapter, materializedAttachments.text, modelId, resolvedReasoningEffort, attachments, resolvedSystemPrompt, context ? { source: context.source } : undefined);
901
- reportLog({
902
- level: 'info',
903
- sessionId,
904
- wsEvent: 'chat.send.done',
905
- metadata: { reqId: req.id },
906
- });
907
- }
908
- catch (err) {
909
- let failedRevision = 0;
910
- for (const [index, envelope] of committedUserEnvelopes.entries()) {
911
- const bodyRevision = upsertMessage(sessionId, envelope, 'failed');
912
- if (index === 0)
913
- failedRevision = bodyRevision;
914
- }
915
- requestSessionIndexPublish(runtime.client);
916
- const failureMessage = formatAgentSendFailure(requestedAgentType, err);
917
- await handleSendFailure(err, false);
918
- runtime.client.sendRes({
919
- type: 'res',
920
- id: replyId,
921
- ok: true,
922
- payload: {
923
- ...durableAck('failed', failedRevision),
924
- failureMessage,
925
- },
926
- });
927
- return;
928
- }
929
- const ack = markAccepted(sendResult ?? undefined);
930
- runtime.client.sendRes({
931
- type: 'res',
932
- id: replyId,
933
- ok: true,
934
- payload: {
935
- ...ack,
936
- ...(materializedAttachments.localized ? { localizedAttachments: true } : {}),
937
- ...(sendResult?.deliveryMode === 'external_owner_queue'
938
- ? {
939
- queued: true,
940
- deliveryMode: sendResult.deliveryMode,
941
- queuedSubmissionId: sendResult.queuedSubmissionId,
942
- }
943
- : {}),
944
- },
945
- });
946
- reportLog({
947
- level: 'info',
948
- sessionId,
949
- wsEvent: 'chat.send.res',
950
- metadata: { reqId: req.id, ok: true },
951
- });
952
- return;
953
- }
954
- let committedAck;
955
- try {
956
- committedAck = commitUserBeforeDispatch();
957
- }
958
- catch (err) {
959
- runtime.processedReqIds.delete(req.id);
960
- runtime.client.sendRes({
961
- type: 'res',
962
- id: replyId,
963
- ok: false,
964
- error: `Durable message commit failed: ${err instanceof Error ? err.message : String(err)}`,
965
- });
966
- return;
967
- }
968
- markDispatchPending();
969
- if (context?.source === 'room_activation') {
970
- configureAdapterForSession(session.adapter, sessionId, requestedAgentType, incomingExternalChannel, incomingReplyTarget, managedRoomContextToken, context.managedAgentPolicy, runtime.managedRoomExecutor);
971
- replaceManagedRoomContextToken(runtime, session, managedRoomContextToken);
972
- transferManagedRoomTokenToTurn();
973
- }
974
- markAccepted();
975
- runtime.client.sendRes({
976
- type: 'res',
977
- id: replyId,
978
- ok: true,
979
- payload: {
980
- ...committedAck,
981
- ...(materializedAttachments.localized ? { localizedAttachments: true } : {}),
982
- },
983
- });
984
- reportLog({
985
- level: 'info',
986
- sessionId,
987
- wsEvent: 'chat.send.res',
988
- metadata: { reqId: req.id, ok: true },
989
- });
990
- const asyncAttachments = materializedAttachments.attachments;
991
- const sendPromise = sendAdapterTurn(session.adapter, materializedAttachments.text, modelId, resolvedReasoningEffort, asyncAttachments, resolvedSystemPrompt, context ? { source: context.source } : undefined);
992
- void sendPromise
993
- .then(() => {
994
- reportLog({
995
- level: 'info',
996
- sessionId,
997
- wsEvent: 'chat.send.done',
998
- metadata: { reqId: req.id },
999
- });
1000
- })
1001
- .catch((err) => {
1002
- upsertMessage(sessionId, userEnvelope, 'failed');
1003
- requestSessionIndexPublish(runtime.client);
1004
- void handleSendFailure(err, false);
1005
- });
1006
- }
1007
- export async function handleChatAbort(runtime, req) {
1008
- const { sessionId } = req.params;
1009
- const session = runtime.sessions.get(sessionId);
1010
- if (session) {
1011
- // Revoke the daemon-side Turn capability before waiting for the Agent to
1012
- // acknowledge interruption. No new Room action may start during stop.
1013
- revokeManagedRoomContextToken(runtime, session);
1014
- try {
1015
- await session.adapter.stop();
1016
- }
1017
- catch {
1018
- runtime.client.sendRes({
1019
- type: 'res',
1020
- id: req.id,
1021
- ok: false,
1022
- error: 'agent_stop_failed',
1023
- });
1024
- return;
1025
- }
1026
- emitSyntheticAbort(runtime, sessionId);
1027
- runtime.activityPublisher?.publish(sessionId, null);
1028
- }
1029
- runtime.client.sendRes({ type: 'res', id: req.id, ok: true });
1030
- }
1
+ import de from"node:os";import{createHash as K}from"node:crypto";import{createAgent as Ee}from"../../agents/adapter.js";import{buildApprovalPendingPayload as xe,buildUserMessagePayload as ie}from"@shennian/wire";import{ManagerModeAdapter as Me}from"../../agents/manager.js";import{reportLog as D}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as Te}from"../../native-fusion/parsers.js";import{appendMessage as b,getDaemonInstallationId as le,getMessageDeliveryStatus as _e,recordSession as H,upsertMessage as z}from"../store.js";import{mergeProjectedSessions as De}from"../projection.js";import{buildManagedAgentEnv as be}from"../../agents/config-status.js";import{parsePersonalAttachmentReferences as qe}from"../attachment-reference.js";import{assertFinishedAttachmentReferences as Pe,markAttachmentReferences as $e}from"../attachment-transfer-store.js";import{requestSessionIndexPublish as M}from"../index-publisher.js";import{writeToolDetail as ve}from"../tool-detail-store.js";import{getPersonalSyncIdentity as ce}from"../../personal-sync/epoch.js";import{buildManagedAgentPermissionPrompt as Ce}from"../../room/managed-prompt-composer.js";import{getManagedRoomIpcRoot as We,MANAGED_ROOM_IPC_ROOT_ENV as Oe}from"../../daemon-ipc/paths.js";function Ne(e,t,a){if(!e.managedAgentPolicy)throw new Error("managed_agent_policy_missing");const s=e.managedRoomPromptSnapshot;if(!s)throw new Error("managed_room_prompt_snapshot_missing");if(!t?.trim())throw new Error("managed_room_prompt_incomplete");if(!a||!/^mrc_[A-Za-z0-9_-]{43}$/.test(a))throw new Error("managed_room_context_unavailable");const r=K("sha256").update(JSON.stringify({requestedPolicy:e.managedAgentPolicy.requestedPolicy,enforcement:e.managedAgentPolicy.enforcement})).digest("hex"),l=K("sha256").update(e.managedAgentPolicy.adapterPolicy.workDir).digest("hex");if(s.runtimePermissionPolicyHash!==r||s.resolvedWorkDirHash!==l||s.enforcementLevel!==e.managedAgentPolicy.enforcement.level)throw new Error("managed_room_prompt_snapshot_mismatch")}function Fe(e){const t=e.indexOf(`
2
+ `),a=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,a)}function ue(e,t,a){return`${e}:${K("sha256").update(`${t}\0${a??""}`).digest("hex")}`}function He(e,t,a={}){if(e.state==="tool-call"||e.state==="tool-result"){const s={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:s,status:e.state==="tool-call"?"running":"completed",detailAvailable:!0,...e.durationMs!==void 0?{durationMs:e.durationMs}:{},...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...a}}return{...e,sessionId:t,...a}}const ze=3e4;function Be(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function ge(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function fe(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function Le(e,t){H({sessionId:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,managerDefaultWorkerAgentType:t.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:t.managerDefaultWorkerModelId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,sessionMode:t.sessionMode,managerConfig:t.managerConfig,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function me(e){return be(e)}function pe(e,t,a){return{}}function X(e,t,a,s,r,l,c,R){const S=a==="codex";e.configure?.({sessionId:t,externalChannel:s??null,env:{...me(a),...pe(t,s,r),...l&&!S?{SHENNIAN_MANAGED_ROOM_CONTEXT:l,[Oe]:We()}:{}},...c?{managedAgentPolicy:c}:{},...S?{managedRoomContextToken:l??null,managedRoomExecutor:R??null}:{}})}function he(e,t,a){const s=t.managedRoomContextToken;s&&s!==a&&e.revokeManagedRoomContextToken?.(s),t.managedRoomContextToken=a}function v(e,t){const a=t?.managedRoomContextToken;!t||!a||(t.managedRoomContextToken=null,e.revokeManagedRoomContextToken?.(a))}function Ue(e,t,a){if(e!=="claude"||!a)return t;const s=t.trim();return!!s&&s.startsWith("-")&&!s.includes("/")?Te(a)??t:t}function Je(e,t,a,s){let r=null;function l(n,o={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:He(n,t,o),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function c(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function R(n){if(!n.currentRunId||!n.currentRunPhase)return;const o=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:o,runPhase:n.currentRunPhase},seq:o,id:`agent-heartbeat-${n.currentRunId}-${o}-${Date.now()}`})}function S(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{R(n)},ze),n.heartbeatTimer.unref?.())}function h(n){const o=n?.pendingTextEvent;!n||!o||!o.text||(b(t,{id:`agent-${o.runId}-${o.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:o.text}),M(e.client),l({state:"delta",runId:o.runId,seq:o.seq,text:o.text,thinking:o.thinking||void 0}),n.pendingTextEvent=null)}s.on("agentEvent",n=>{const o=e.sessions.get(t),q=Be(n),T=n.state==="final"||n.state==="error"||n.state==="aborted";if(o&&(o.nextEventSeq=n.seq+1,T||(o.currentRunId=n.runId),!T&&q&&(o.currentRunPhase=q,S(o)),n.agentSessionId&&(o.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state==="delta"&&n.thinking)return;n.state!=="delta"&&D({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}});let k=!1;if(n.state==="tool-call"||n.state==="tool-result"){const m=`agent-${n.runId}-${n.seq}`;ve({sessionId:t,toolCallId:m,detailRevision:n.seq,ts:Date.now(),name:n.name??"tool",...n.args!==void 0?{args:n.args}:{},...n.result!==void 0?{result:n.result}:{},status:n.state==="tool-call"?"running":"completed",...n.durationMs!==void 0?{durationMs:n.durationMs}:{},detailRef:{runId:n.runId,sourceSeq:n.seq}}),b(t,{id:m,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,toolCallId:m,status:n.state==="tool-call"?"running":"completed",...n.durationMs!==void 0?{durationMs:n.durationMs}:{},detailRef:{runId:n.runId,sourceSeq:n.seq},detailRevision:n.seq,detailAvailable:!0,resultOmitted:n.state==="tool-result"})}),k=!0}else n.state==="approval-pending"?(b(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:xe(n.approval)}),k=!0,o&&H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,attentionId:ue("approval",n.runId,n.seq)})):(n.state==="error"||n.state==="aborted")&&n.message&&(b(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message}),k=!0);k&&M(e.client);const A=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(A,(e.runTextAcc.get(A)??"")+n.text),n.agentSessionId&&n.agentSessionId!==r&&(r=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,fe(a,n.source),n.agentSessionId),o&&H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const m=n.text??"";if(!m)return;o?.pendingTextEvent&&o.pendingTextEvent.runId!==n.runId&&h(o),o&&!o.pendingTextEvent&&(o.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:!1}),o?.pendingTextEvent&&(o.pendingTextEvent.text+=m,o.pendingTextEvent.seq=n.seq);return}let C={};if(n.state==="final"){h(o);const m=e.runTextAcc.get(A)??"";m&&(C={messageSummary:Fe(m)}),e.runTextAcc.delete(A)}else n.state==="error"||n.state==="aborted"?(h(o),e.runTextAcc.delete(A)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&h(o);T&&o?.currentRunId===n.runId&&(v(e,o),o.currentRunId=null,o.currentRunPhase=null,o.nextEventSeq=0,c(o),e.chatQueue?.noteTerminal(t),H({sessionId:t,agentType:a,workDir:o.workDir,agentSessionId:o.agentSessionId,status:n.state==="final"?"completed":"failed",attentionId:ue("terminal",n.runId),lastActivityAt:new Date().toISOString()}),M(e.client)),l(n,C)}),s.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${a}: ${n.message}`),v(e,e.sessions.get(t)),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function Qe(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const a=e.processedReqIds.values().next().value;e.processedReqIds.delete(a)}}async function B(e,t){v(e,t),t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}function ye(e,t,a,s,r,l,c){return c?e.send(t,a,s,r,l,c):l?.trim()?e.send(t,a,s,r,l):r?.length?e.send(t,a,s,r):s?e.send(t,a,s):e.send(t,a)}function Ve(e,t,a,s,r,l,c){return e.prepareSend?e.prepareSend(t,a,s,r,l,c):Promise.resolve()}async function je(e,t,a,s,r,l,c,R,S){e.evictIdleSessions();const h=Ee(a);if(!h)throw new Error(`Unsupported agent: ${a}`);const n=s==="manager"&&a!=="manager"?new Me(h,a==="claude"?"claude":"codex"):h;X(n,t,a,c,R,null,S,e.managedRoomExecutor),await n.start(t,r,l);const o={adapter:n,workDir:r,agentType:a,sessionMode:s,agentSessionId:l??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:c??null,externalReplyTarget:R??null,externalChannelEnv:{...me(a),...pe(t,c,R)},managedRoomContextToken:null};return e.sessions.set(t,o),Je(e,t,a,n),o}function Ge(e,t){const a=e.sessions.get(t),s=a?.currentRunId;if(!a||!s)return;const r=a.nextEventSeq;e.runTextAcc.delete(`${t}:${s}`),v(e,a),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:s,seq:r},seq:r,id:`agent-evt-${s}-${r}`}),e.chatQueue?.noteTerminal(t)}async function pt(e,t,a){const s=a?t.params.managedRoomContextToken??null:null;let r=!1;try{await Ke(e,t,a,()=>{r=!0})}finally{s&&!r&&e.revokeManagedRoomContextToken?.(s)}}async function Ke(e,t,a,s){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}const{sessionId:r,text:l,agentType:c,sessionMode:R,managerConfig:S,workDir:h,agentSessionId:n,modelId:o,systemPrompt:q,managerDefaultWorkerAgentType:T,managerDefaultWorkerModelId:k,reasoningEffort:A,clientMessageId:C,sessionListProjection:m,waitForDispatch:Y,responseId:Ie,managedRoomContextToken:P,suppressExternalOwnerEcho:Re,queuedBatchItems:L,managedWorkerParentSessionId:W}=t.params,y=Ie||t.id;De(m);const O=null,U="";if(a?.source==="room_activation"&&(Ne(a,q,P),c==="manager"))throw new Error("managed_agent_manager_mode_unsupported");if(Qe(e,t.id),!r||!l){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:"sessionId and text are required"});return}const g=c,$=g==="manager"||R==="manager"?"manager":"standard";$==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(r,T??null,k??null);const N=(g==="claude"||g==="codex"||g==="workbuddy")&&typeof A=="string"&&A.trim()?A.trim():void 0,w=e.resolvePath(Ue(g,h||de.homedir(),n)||de.homedir()),_=a?.managedAgentPolicy??(W?e.managerRuntime?.getManagedAgentPolicyForWorker(W,w)??null:null);if(W&&!_)throw new Error("managed_worker_permission_context_unavailable");const J=W&&_?Ce({requestedPolicy:_.requestedPolicy,enforcement:_.enforcement,resolvedWorkDir:_.adapterPolicy.workDir}):q,Se=w,F=qe(t.params.attachments);F?.length&&Pe(w,F);const p={text:l,attachments:F,localized:!1};let i=e.sessions.get(r);if(i){if(i.lastActiveAt=Date.now(),i.agentType!==g||(i.sessionMode??(i.agentType==="manager"?"manager":"standard"))!==$||i.workDir!==w||JSON.stringify(i.externalChannel??null)!==JSON.stringify(O??null)){e.sessions.delete(r);try{await B(e,i)}catch{e.processedReqIds.delete(t.id)}i=void 0}else if(n&&i.agentSessionId!==n)try{await i.adapter.resume(n),i.agentSessionId=n}catch{e.sessions.delete(r);try{await B(e,i)}catch{e.processedReqIds.delete(t.id)}i=void 0}}if(!i)try{i=await je(e,r,g,$,w,n,O,U,_)}catch(d){const f=d instanceof Error&&d.message.startsWith("Unsupported agent:")?d.message:`Failed to start ${c}: ${d instanceof Error?d.message:String(d)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${r} agentType=${c} workDir=${w} agentSessionId=${n??""}: ${f}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:f,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:f});return}const I={id:C??`user-${t.id}`,sessionId:r,role:"user",ts:Date.now(),payload:ie(p.text,p.attachments)},Z=Array.isArray(L)&&L.length>0?L.map(d=>({id:d.clientMessageId??d.id,sessionId:r,role:"user",ts:Date.parse(d.createdAt),payload:ie(d.text,d.attachments)})):null,Q=Z??[I],E=_e(r,I.id),ee=E.state==="queued";if(E.state!=="not_found"&&!ee){e.client.sendRes({type:"res",id:y,ok:!0,payload:E});return}if(Y){a?.source==="room_activation"&&X(i.adapter,r,g,O,U,P,a.managedAgentPolicy,e.managedRoomExecutor);try{await Ve(i.adapter,p.text,o,N,p.attachments,J,a?{source:a.source}:void 0)}catch(d){e.processedReqIds.delete(t.id),e.sessions.delete(r),await B(e,i),e.client.sendRes({type:"res",id:y,ok:!1,error:d instanceof Error?d.message:String(d)});return}}D({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:g,sessionMode:$,managerConfig:S??null,modelId:o,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:k??null,reasoningEffort:N,...a?.source==="room_activation"?{managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:{}}});const V=(d,f)=>({...ce(le()),sessionId:r,clientMessageId:I.id,messageId:I.id,deliveryState:d,bodyRevision:f,committedAt:new Date(I.ts).toISOString()}),te=d=>{e.nativeFusion?.registerManagedSend({sessionId:r,agentType:g,sourceAgentType:fe(g,o),canonicalMessageId:I.id,sourceSessionKey:i.agentSessionId??n??null,text:p.text,managedEchoPolicy:d?.deliveryMode==="external_owner_queue"&&!Re?"import_following":"suppress_following",externalRunId:d?.deliveryMode==="external_owner_queue"?`codex-external-${d.queuedSubmissionId??I.id}`:null})},ne=()=>{i.currentRunId||(i.currentRunId=`dispatch:${t.id}`,i.currentRunPhase="thinking",i.nextEventSeq=0)},ae=()=>{if(te(),ee)return{...ce(le()),sessionId:r,clientMessageId:E.clientMessageId,messageId:E.messageId,deliveryState:E.deliveryState,bodyRevision:E.bodyRevision,committedAt:E.committedAt};try{Le(e,{sessionId:r,agentType:g,sessionMode:$,managerConfig:S??null,workDir:Se,agentSessionId:i.agentSessionId??n??null,modelId:o,managerDefaultWorkerAgentType:T??null,managerDefaultWorkerModelId:k??null});let d=0;for(const[f,u]of Q.entries()){const x=Z?z(r,u,"committed"):b(r,u,"committed");f===0&&(d=x)}return $e(w,F??[]),M(e.client),V("committed",d)}catch(d){throw e.nativeFusion?.cancelManagedSend?.(I.id),d}},re=d=>{i.currentRunId||(i.nextEventSeq=0),te(d);const f=d?.deliveryMode==="external_owner_queue"?"queued":"executing";let u=0;for(const[x,j]of Q.entries()){const G=z(r,j,f);x===0&&(u=G)}return M(e.client),V(f,u)},oe=async(d,f)=>{e.nativeFusion?.cancelManagedSend?.(I.id);const u=ge(g,d);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${r} agentType=${c} workDir=${w} agentSessionId=${i.agentSessionId??n??""}: ${u}`),e.sessions.delete(r);try{await B(e,i)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:u,runId:"",seq:0}}),!f){const x={id:`agent-error-${t.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:u};b(r,x),M(e.client)}f&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:u}))};if(Y){try{ae()}catch(u){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:`Durable message commit failed: ${u instanceof Error?u.message:String(u)}`});return}ne();let d;try{a?.source==="room_activation"&&(he(e,i,P),s());const u=p.attachments;d=await ye(i.adapter,p.text,o,N,u,J,a?{source:a.source}:void 0),D({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(u){let x=0;for(const[G,ke]of Q.entries()){const we=z(r,ke,"failed");G===0&&(x=we)}M(e.client);const j=ge(g,u);await oe(u,!1),e.client.sendRes({type:"res",id:y,ok:!0,payload:{...V("failed",x),failureMessage:j}});return}const f=re(d??void 0);e.client.sendRes({type:"res",id:y,ok:!0,payload:{...f,...p.localized?{localizedAttachments:!0}:{},...d?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:d.deliveryMode,queuedSubmissionId:d.queuedSubmissionId}:{}}}),D({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}let se;try{se=ae()}catch(d){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:y,ok:!1,error:`Durable message commit failed: ${d instanceof Error?d.message:String(d)}`});return}ne(),a?.source==="room_activation"&&(X(i.adapter,r,g,O,U,P,a.managedAgentPolicy,e.managedRoomExecutor),he(e,i,P),s()),re(),e.client.sendRes({type:"res",id:y,ok:!0,payload:{...se,...p.localized?{localizedAttachments:!0}:{}}}),D({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const Ae=p.attachments;ye(i.adapter,p.text,o,N,Ae,J,a?{source:a.source}:void 0).then(()=>{D({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(d=>{z(r,I,"failed"),M(e.client),oe(d,!1)})}async function ht(e,t){const{sessionId:a}=t.params,s=e.sessions.get(a);if(s){v(e,s);try{await s.adapter.stop()}catch{e.client.sendRes({type:"res",id:t.id,ok:!1,error:"agent_stop_failed"});return}Ge(e,a),e.activityPublisher?.publish(a,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{ht as handleChatAbort,pt as handleChatSend,Le as sendSessionUpdateEvent};