shennian 0.2.86 → 0.2.87
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.
- package/dist/src/agents/adapter.d.ts +2 -1
- package/dist/src/commands/daemon.js +1 -0
- package/dist/src/index.js +2 -1
- package/dist/src/native-fusion/config.d.ts +1 -0
- package/dist/src/native-fusion/config.js +5 -0
- package/dist/src/session/handlers/chat.js +67 -2
- package/dist/src/session/manager.js +8 -0
- package/dist/src/session/types.d.ts +4 -1
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { AgentType, ChatAttachmentMeta, ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
|
+
import type { AgentType, ChatAttachmentMeta, ExternalChannelSessionStatus, SessionRunPhase } from '@shennian/wire';
|
|
3
3
|
export type AgentEvent = {
|
|
4
4
|
state: string;
|
|
5
5
|
runId: string;
|
|
6
6
|
seq: number;
|
|
7
7
|
text?: string;
|
|
8
8
|
thinking?: boolean;
|
|
9
|
+
runPhase?: SessionRunPhase;
|
|
9
10
|
name?: string;
|
|
10
11
|
args?: Record<string, unknown>;
|
|
11
12
|
result?: string;
|
package/dist/src/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const AUTO_UPGRADE_INITIAL_DELAY_MS = 30_000;
|
|
|
25
25
|
const AUTO_UPGRADE_POLL_INTERVAL_MS = 5 * 60_000;
|
|
26
26
|
import { getCachedAgentInfos, resolveAgentInfos } from './agents/model-registry.js';
|
|
27
27
|
import { initCliLogReporter, reportLog } from './log-reporter.js';
|
|
28
|
+
import { isNativeFusionEnabled } from './native-fusion/config.js';
|
|
28
29
|
import { NativeSessionFusionService } from './native-fusion/service.js';
|
|
29
30
|
import { startDaemonLogRetention } from './daemon-log.js';
|
|
30
31
|
const SHENNIAN_DIR = getShennianDir();
|
|
@@ -285,7 +286,7 @@ program
|
|
|
285
286
|
},
|
|
286
287
|
});
|
|
287
288
|
nativeFusion =
|
|
288
|
-
|
|
289
|
+
isNativeFusionEnabled()
|
|
289
290
|
? new NativeSessionFusionService(client)
|
|
290
291
|
: null;
|
|
291
292
|
const sessionManager = new SessionManager(client, nativeFusion, currentCliVersion);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isNativeFusionEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
@@ -49,6 +49,18 @@ function buildRelayAgentPayload(event, sessionId, extra = {}) {
|
|
|
49
49
|
}
|
|
50
50
|
return { ...event, sessionId, ...extra };
|
|
51
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
|
+
}
|
|
52
64
|
function formatAgentSendFailure(agentType, err) {
|
|
53
65
|
const raw = err instanceof Error ? err.message : String(err);
|
|
54
66
|
if (agentType === 'pi' &&
|
|
@@ -194,6 +206,38 @@ function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
|
|
|
194
206
|
id: `agent-evt-${event.runId}-${event.seq}`,
|
|
195
207
|
});
|
|
196
208
|
}
|
|
209
|
+
function stopActivityHeartbeat(activeSession) {
|
|
210
|
+
if (!activeSession?.heartbeatTimer)
|
|
211
|
+
return;
|
|
212
|
+
clearInterval(activeSession.heartbeatTimer);
|
|
213
|
+
activeSession.heartbeatTimer = null;
|
|
214
|
+
}
|
|
215
|
+
function sendActivityHeartbeat(activeSession) {
|
|
216
|
+
if (!activeSession.currentRunId || !activeSession.currentRunPhase)
|
|
217
|
+
return;
|
|
218
|
+
const seq = activeSession.heartbeatSeq++;
|
|
219
|
+
runtime.client.sendAgentEvent({
|
|
220
|
+
type: 'event',
|
|
221
|
+
event: 'agent',
|
|
222
|
+
payload: {
|
|
223
|
+
state: 'heartbeat',
|
|
224
|
+
sessionId,
|
|
225
|
+
runId: activeSession.currentRunId,
|
|
226
|
+
seq,
|
|
227
|
+
runPhase: activeSession.currentRunPhase,
|
|
228
|
+
},
|
|
229
|
+
seq,
|
|
230
|
+
id: `agent-heartbeat-${activeSession.currentRunId}-${seq}-${Date.now()}`,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function ensureActivityHeartbeat(activeSession) {
|
|
234
|
+
if (!activeSession || activeSession.heartbeatTimer)
|
|
235
|
+
return;
|
|
236
|
+
activeSession.heartbeatTimer = setInterval(() => {
|
|
237
|
+
sendActivityHeartbeat(activeSession);
|
|
238
|
+
}, SESSION_ACTIVITY_HEARTBEAT_INTERVAL_MS);
|
|
239
|
+
activeSession.heartbeatTimer.unref?.();
|
|
240
|
+
}
|
|
197
241
|
function flushTextBuffer(activeSession) {
|
|
198
242
|
const textBuffer = activeSession?.pendingTextEvent;
|
|
199
243
|
if (!activeSession || !textBuffer || !textBuffer.text)
|
|
@@ -209,9 +253,16 @@ function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
|
|
|
209
253
|
}
|
|
210
254
|
adapter.on('agentEvent', (event) => {
|
|
211
255
|
const activeSession = runtime.sessions.get(sessionId);
|
|
256
|
+
const runPhase = runPhaseFromAgentEvent(event);
|
|
257
|
+
const isTerminalEvent = event.state === 'final' || event.state === 'error' || event.state === 'aborted';
|
|
212
258
|
if (activeSession) {
|
|
213
|
-
activeSession.currentRunId = event.runId;
|
|
214
259
|
activeSession.nextEventSeq = event.seq + 1;
|
|
260
|
+
if (!isTerminalEvent)
|
|
261
|
+
activeSession.currentRunId = event.runId;
|
|
262
|
+
if (!isTerminalEvent && runPhase) {
|
|
263
|
+
activeSession.currentRunPhase = runPhase;
|
|
264
|
+
ensureActivityHeartbeat(activeSession);
|
|
265
|
+
}
|
|
215
266
|
if (event.agentSessionId)
|
|
216
267
|
activeSession.agentSessionId = event.agentSessionId;
|
|
217
268
|
}
|
|
@@ -328,10 +379,12 @@ function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
|
|
|
328
379
|
else if (event.state === 'tool-call' || event.state === 'tool-result' || event.state === 'approval-pending') {
|
|
329
380
|
flushTextBuffer(activeSession);
|
|
330
381
|
}
|
|
331
|
-
if (
|
|
382
|
+
if (isTerminalEvent &&
|
|
332
383
|
activeSession?.currentRunId === event.runId) {
|
|
333
384
|
activeSession.currentRunId = null;
|
|
385
|
+
activeSession.currentRunPhase = null;
|
|
334
386
|
activeSession.nextEventSeq = 0;
|
|
387
|
+
stopActivityHeartbeat(activeSession);
|
|
335
388
|
runtime.chatQueue?.noteTerminal(sessionId);
|
|
336
389
|
}
|
|
337
390
|
sendAgentEvent(event, extra);
|
|
@@ -355,6 +408,10 @@ function rememberProcessedReqId(runtime, reqId) {
|
|
|
355
408
|
}
|
|
356
409
|
}
|
|
357
410
|
async function disposeSession(session) {
|
|
411
|
+
if (session.heartbeatTimer) {
|
|
412
|
+
clearInterval(session.heartbeatTimer);
|
|
413
|
+
session.heartbeatTimer = null;
|
|
414
|
+
}
|
|
358
415
|
session.adapter.removeAllListeners();
|
|
359
416
|
await session.adapter.stop().catch(() => { });
|
|
360
417
|
}
|
|
@@ -372,7 +429,10 @@ async function createActiveSession(runtime, sessionId, agentType, resolvedWorkDi
|
|
|
372
429
|
agentSessionId: incomingAgentSid ?? null,
|
|
373
430
|
lastActiveAt: Date.now(),
|
|
374
431
|
currentRunId: null,
|
|
432
|
+
currentRunPhase: null,
|
|
375
433
|
nextEventSeq: 0,
|
|
434
|
+
heartbeatSeq: 0,
|
|
435
|
+
heartbeatTimer: null,
|
|
376
436
|
pendingTextEvent: null,
|
|
377
437
|
externalChannel: externalChannel ?? null,
|
|
378
438
|
externalReplyTarget: externalReplyTarget ?? null,
|
|
@@ -394,7 +454,12 @@ function emitSyntheticAbort(runtime, sessionId) {
|
|
|
394
454
|
runtime.runTextAcc.delete(`${sessionId}:${runId}`);
|
|
395
455
|
session.pendingTextEvent = null;
|
|
396
456
|
session.currentRunId = null;
|
|
457
|
+
session.currentRunPhase = null;
|
|
397
458
|
session.nextEventSeq = 0;
|
|
459
|
+
if (session.heartbeatTimer) {
|
|
460
|
+
clearInterval(session.heartbeatTimer);
|
|
461
|
+
session.heartbeatTimer = null;
|
|
462
|
+
}
|
|
398
463
|
runtime.client.sendAgentEvent({
|
|
399
464
|
type: 'event',
|
|
400
465
|
event: 'agent',
|
|
@@ -228,6 +228,10 @@ export class SessionManager {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
if (oldest) {
|
|
231
|
+
if (oldest.session.heartbeatTimer) {
|
|
232
|
+
clearInterval(oldest.session.heartbeatTimer);
|
|
233
|
+
oldest.session.heartbeatTimer = null;
|
|
234
|
+
}
|
|
231
235
|
oldest.session.adapter.removeAllListeners();
|
|
232
236
|
oldest.session.adapter.stop().catch(() => { });
|
|
233
237
|
this.sessions.delete(oldest.key);
|
|
@@ -242,6 +246,10 @@ export class SessionManager {
|
|
|
242
246
|
}
|
|
243
247
|
cleanup() {
|
|
244
248
|
for (const [, session] of this.sessions) {
|
|
249
|
+
if (session.heartbeatTimer) {
|
|
250
|
+
clearInterval(session.heartbeatTimer);
|
|
251
|
+
session.heartbeatTimer = null;
|
|
252
|
+
}
|
|
245
253
|
session.adapter.stop().catch(() => { });
|
|
246
254
|
}
|
|
247
255
|
this.sessions.clear();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentType, ExternalChannelSessionStatus } from '@shennian/wire';
|
|
1
|
+
import type { AgentType, ExternalChannelSessionStatus, SessionRunPhase } from '@shennian/wire';
|
|
2
2
|
import type { AgentAdapter } from '../agents/adapter.js';
|
|
3
3
|
import type { CliRelayClient } from '../relay/client.js';
|
|
4
4
|
import type { NativeSessionFusionService } from '../native-fusion/service.js';
|
|
@@ -11,7 +11,10 @@ export type ActiveSession = {
|
|
|
11
11
|
agentSessionId: string | null;
|
|
12
12
|
lastActiveAt: number;
|
|
13
13
|
currentRunId: string | null;
|
|
14
|
+
currentRunPhase: SessionRunPhase | null;
|
|
14
15
|
nextEventSeq: number;
|
|
16
|
+
heartbeatSeq: number;
|
|
17
|
+
heartbeatTimer: ReturnType<typeof setInterval> | null;
|
|
15
18
|
pendingTextEvent: {
|
|
16
19
|
runId: string;
|
|
17
20
|
seq: number;
|