@nextclaw/ncp-toolkit 0.4.9 → 0.4.11

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 (3) hide show
  1. package/dist/index.d.ts +42 -34
  2. package/dist/index.js +230 -154
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -8,32 +8,33 @@ declare class DefaultNcpAgentConversationStateManager implements NcpAgentConvers
8
8
  private readonly listeners;
9
9
  private readonly toolCallMessageIdByCallId;
10
10
  private readonly toolCallArgsRawByCallId;
11
+ private lastSettledRunId;
11
12
  private snapshotCache;
12
13
  private snapshotVersion;
13
14
  private stateVersion;
14
- getSnapshot(): NcpAgentConversationSnapshot;
15
- subscribe(listener: (snapshot: NcpAgentConversationSnapshot) => void): () => boolean;
16
- reset(): void;
17
- hydrate(payload: NcpAgentConversationHydrationParams): void;
18
- dispatch(event: NcpEndpointEvent): Promise<void>;
19
- handleMessageSent(payload: NcpMessageSentPayload): void;
20
- handleMessageAbort(payload: NcpMessageAbortPayload): void;
21
- handleMessageTextStart(payload: NcpTextStartPayload): void;
22
- handleMessageTextDelta(payload: NcpTextDeltaPayload): void;
23
- handleMessageTextEnd(payload: NcpTextEndPayload): void;
24
- handleMessageReasoningStart(payload: NcpReasoningStartPayload): void;
25
- handleMessageReasoningDelta(payload: NcpReasoningDeltaPayload): void;
26
- handleMessageReasoningEnd(payload: NcpReasoningEndPayload): void;
27
- handleMessageToolCallStart(payload: NcpToolCallStartPayload): void;
28
- handleMessageToolCallArgs(payload: NcpToolCallArgsPayload): void;
29
- handleMessageToolCallArgsDelta(payload: NcpToolCallArgsDeltaPayload): void;
30
- handleMessageToolCallEnd(payload: NcpToolCallEndPayload): void;
31
- handleMessageToolCallResult(payload: NcpToolCallResultPayload): void;
32
- handleRunStarted(payload: NcpRunStartedPayload): void;
33
- handleRunFinished(_payload: NcpRunFinishedPayload): void;
34
- handleRunError(payload: NcpRunErrorPayload): void;
35
- handleRunMetadata(payload: NcpRunMetadataPayload): void;
36
- handleEndpointError(payload: NcpError): void;
15
+ getSnapshot: () => NcpAgentConversationSnapshot;
16
+ subscribe: (listener: (snapshot: NcpAgentConversationSnapshot) => void) => () => boolean;
17
+ reset: () => void;
18
+ hydrate: (payload: NcpAgentConversationHydrationParams) => void;
19
+ dispatch: (event: NcpEndpointEvent) => Promise<void>;
20
+ handleMessageSent: (payload: NcpMessageSentPayload) => void;
21
+ handleMessageAbort: (payload: NcpMessageAbortPayload) => void;
22
+ handleMessageTextStart: (payload: NcpTextStartPayload) => void;
23
+ handleMessageTextDelta: (payload: NcpTextDeltaPayload) => void;
24
+ handleMessageTextEnd: (payload: NcpTextEndPayload) => void;
25
+ handleMessageReasoningStart: (payload: NcpReasoningStartPayload) => void;
26
+ handleMessageReasoningDelta: (payload: NcpReasoningDeltaPayload) => void;
27
+ handleMessageReasoningEnd: (payload: NcpReasoningEndPayload) => void;
28
+ handleMessageToolCallStart: (payload: NcpToolCallStartPayload) => void;
29
+ handleMessageToolCallArgs: (payload: NcpToolCallArgsPayload) => void;
30
+ handleMessageToolCallArgsDelta: (payload: NcpToolCallArgsDeltaPayload) => void;
31
+ handleMessageToolCallEnd: (payload: NcpToolCallEndPayload) => void;
32
+ handleMessageToolCallResult: (payload: NcpToolCallResultPayload) => void;
33
+ handleRunStarted: (payload: NcpRunStartedPayload) => void;
34
+ handleRunFinished: (payload: NcpRunFinishedPayload) => void;
35
+ handleRunError: (payload: NcpRunErrorPayload) => void;
36
+ handleRunMetadata: (payload: NcpRunMetadataPayload) => void;
37
+ handleEndpointError: (payload: NcpError) => void;
37
38
  private applyToolCallArgs;
38
39
  private ensureStreamingMessage;
39
40
  private resolveToolCallTargetMessage;
@@ -42,6 +43,8 @@ declare class DefaultNcpAgentConversationStateManager implements NcpAgentConvers
42
43
  private replaceStreamingMessage;
43
44
  private setError;
44
45
  private clearActiveRun;
46
+ private isSettledRunId;
47
+ private markRunAsSettled;
45
48
  private settleStreamingMessage;
46
49
  private notifyListeners;
47
50
  }
@@ -99,6 +102,10 @@ interface AgentSessionStore {
99
102
  type DefaultNcpAgentBackendConfig = {
100
103
  createRuntime: CreateRuntimeFn;
101
104
  sessionStore: AgentSessionStore;
105
+ onSessionRunStatusChanged?: (payload: {
106
+ sessionKey: string;
107
+ status: "running" | "idle";
108
+ }) => void;
102
109
  endpointId?: string;
103
110
  version?: string;
104
111
  metadata?: Record<string, unknown>;
@@ -110,28 +117,29 @@ declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessi
110
117
  endpointKind: "agent";
111
118
  };
112
119
  private readonly sessionStore;
120
+ private readonly onSessionRunStatusChanged;
113
121
  private readonly sessionRegistry;
114
122
  private readonly executor;
115
123
  private readonly publisher;
116
124
  private started;
117
125
  constructor(config: DefaultNcpAgentBackendConfig);
118
- start(): Promise<void>;
119
- stop(): Promise<void>;
120
- emit(event: NcpEndpointEvent): Promise<void>;
121
- subscribe(listener: (event: NcpEndpointEvent) => void): () => void;
122
- send(envelope: NcpRequestEnvelope, options?: NcpAgentRunSendOptions): AsyncIterable<NcpEndpointEvent>;
123
- abort(payload: NcpMessageAbortPayload): Promise<void>;
126
+ start: () => Promise<void>;
127
+ stop: () => Promise<void>;
128
+ emit: (event: NcpEndpointEvent) => Promise<void>;
129
+ subscribe: (listener: (event: NcpEndpointEvent) => void) => () => void;
130
+ send: (envelope: NcpRequestEnvelope, options?: NcpAgentRunSendOptions) => AsyncIterable<NcpEndpointEvent>;
131
+ abort: (payload: NcpMessageAbortPayload) => Promise<void>;
124
132
  stream: (payloadOrParams: NcpStreamRequestPayload | {
125
133
  payload: NcpStreamRequestPayload;
126
134
  signal: AbortSignal;
127
135
  }, opts?: NcpAgentRunStreamOptions) => AsyncIterable<NcpEndpointEvent>;
128
- listSessions(): Promise<NcpSessionSummary[]>;
129
- listSessionMessages(sessionId: string): Promise<NcpMessage[]>;
130
- getSession(sessionId: string): Promise<NcpSessionSummary | null>;
136
+ listSessions: () => Promise<NcpSessionSummary[]>;
137
+ listSessionMessages: (sessionId: string) => Promise<NcpMessage[]>;
138
+ getSession: (sessionId: string) => Promise<NcpSessionSummary | null>;
131
139
  appendMessage: (sessionId: string, message: NcpMessage) => Promise<NcpSessionSummary | null>;
132
140
  updateToolCallResult: (sessionId: string, toolCallId: string, content: unknown) => Promise<NcpSessionSummary | null>;
133
- updateSession(sessionId: string, patch: NcpSessionPatch): Promise<NcpSessionSummary | null>;
134
- deleteSession(sessionId: string): Promise<void>;
141
+ updateSession: (sessionId: string, patch: NcpSessionPatch) => Promise<NcpSessionSummary | null>;
142
+ deleteSession: (sessionId: string) => Promise<void>;
135
143
  private ensureStarted;
136
144
  private startSessionExecution;
137
145
  private finishSessionExecution;
package/dist/index.js CHANGED
@@ -95,10 +95,11 @@ var DefaultNcpAgentConversationStateManager = class {
95
95
  listeners = /* @__PURE__ */ new Set();
96
96
  toolCallMessageIdByCallId = /* @__PURE__ */ new Map();
97
97
  toolCallArgsRawByCallId = /* @__PURE__ */ new Map();
98
+ lastSettledRunId = null;
98
99
  snapshotCache = null;
99
100
  snapshotVersion = -1;
100
101
  stateVersion = 0;
101
- getSnapshot() {
102
+ getSnapshot = () => {
102
103
  if (this.snapshotCache && this.snapshotVersion === this.stateVersion) {
103
104
  return this.snapshotCache;
104
105
  }
@@ -114,12 +115,12 @@ var DefaultNcpAgentConversationStateManager = class {
114
115
  this.snapshotCache = snapshot;
115
116
  this.snapshotVersion = this.stateVersion;
116
117
  return snapshot;
117
- }
118
- subscribe(listener) {
118
+ };
119
+ subscribe = (listener) => {
119
120
  this.listeners.add(listener);
120
121
  return () => this.listeners.delete(listener);
121
- }
122
- reset() {
122
+ };
123
+ reset = () => {
123
124
  if (this.messages.length === 0 && !this.streamingMessage && !this.error && !this.activeRun && this.toolCallMessageIdByCallId.size === 0 && this.toolCallArgsRawByCallId.size === 0) {
124
125
  return;
125
126
  }
@@ -129,10 +130,11 @@ var DefaultNcpAgentConversationStateManager = class {
129
130
  this.activeRun = null;
130
131
  this.toolCallMessageIdByCallId.clear();
131
132
  this.toolCallArgsRawByCallId.clear();
133
+ this.lastSettledRunId = null;
132
134
  this.stateVersion += 1;
133
135
  this.notifyListeners();
134
- }
135
- hydrate(payload) {
136
+ };
137
+ hydrate = (payload) => {
136
138
  this.messages = payload.messages.map(
137
139
  (message) => normalizeConversationMessage(message)
138
140
  );
@@ -145,10 +147,11 @@ var DefaultNcpAgentConversationStateManager = class {
145
147
  } : null;
146
148
  this.toolCallMessageIdByCallId.clear();
147
149
  this.toolCallArgsRawByCallId.clear();
150
+ this.lastSettledRunId = null;
148
151
  this.stateVersion += 1;
149
152
  this.notifyListeners();
150
- }
151
- async dispatch(event) {
153
+ };
154
+ dispatch = async (event) => {
152
155
  const versionBeforeDispatch = this.stateVersion;
153
156
  switch (event.type) {
154
157
  case NcpEventType.MessageSent:
@@ -211,12 +214,12 @@ var DefaultNcpAgentConversationStateManager = class {
211
214
  if (this.stateVersion !== versionBeforeDispatch) {
212
215
  this.notifyListeners();
213
216
  }
214
- }
215
- handleMessageSent(payload) {
217
+ };
218
+ handleMessageSent = (payload) => {
216
219
  this.upsertMessage(payload.message);
217
220
  this.setError(null);
218
- }
219
- handleMessageAbort(payload) {
221
+ };
222
+ handleMessageAbort = (payload) => {
220
223
  const targetMessageId = payload.messageId?.trim();
221
224
  this.clearActiveRun();
222
225
  this.setError(null);
@@ -241,16 +244,16 @@ var DefaultNcpAgentConversationStateManager = class {
241
244
  );
242
245
  }
243
246
  }
244
- }
245
- handleMessageTextStart(payload) {
247
+ };
248
+ handleMessageTextStart = (payload) => {
246
249
  this.ensureStreamingMessage(
247
250
  payload.sessionId,
248
251
  payload.messageId,
249
252
  "streaming"
250
253
  );
251
254
  this.setError(null);
252
- }
253
- handleMessageTextDelta(payload) {
255
+ };
256
+ handleMessageTextDelta = (payload) => {
254
257
  if (!payload.delta) {
255
258
  return;
256
259
  }
@@ -274,8 +277,8 @@ var DefaultNcpAgentConversationStateManager = class {
274
277
  parts: nextParts,
275
278
  status: "streaming"
276
279
  });
277
- }
278
- handleMessageTextEnd(payload) {
280
+ };
281
+ handleMessageTextEnd = (payload) => {
279
282
  if (this.streamingMessage?.id !== payload.messageId) {
280
283
  return;
281
284
  }
@@ -286,15 +289,15 @@ var DefaultNcpAgentConversationStateManager = class {
286
289
  ...this.streamingMessage,
287
290
  status: "pending"
288
291
  });
289
- }
290
- handleMessageReasoningStart(payload) {
292
+ };
293
+ handleMessageReasoningStart = (payload) => {
291
294
  this.ensureStreamingMessage(
292
295
  payload.sessionId,
293
296
  payload.messageId,
294
297
  "streaming"
295
298
  );
296
- }
297
- handleMessageReasoningDelta(payload) {
299
+ };
300
+ handleMessageReasoningDelta = (payload) => {
298
301
  if (!payload.delta) {
299
302
  return;
300
303
  }
@@ -318,13 +321,13 @@ var DefaultNcpAgentConversationStateManager = class {
318
321
  parts: nextParts,
319
322
  status: "streaming"
320
323
  });
321
- }
322
- handleMessageReasoningEnd(payload) {
324
+ };
325
+ handleMessageReasoningEnd = (payload) => {
323
326
  if (this.streamingMessage?.id !== payload.messageId) {
324
327
  return;
325
328
  }
326
- }
327
- handleMessageToolCallStart(payload) {
329
+ };
330
+ handleMessageToolCallStart = (payload) => {
328
331
  const targetMessage = this.resolveToolCallTargetMessage(
329
332
  payload.sessionId,
330
333
  payload.toolCallId,
@@ -344,12 +347,12 @@ var DefaultNcpAgentConversationStateManager = class {
344
347
  status: "streaming"
345
348
  });
346
349
  this.setError(null);
347
- }
348
- handleMessageToolCallArgs(payload) {
350
+ };
351
+ handleMessageToolCallArgs = (payload) => {
349
352
  this.toolCallArgsRawByCallId.set(payload.toolCallId, payload.args);
350
353
  this.applyToolCallArgs(payload.sessionId, payload.toolCallId, payload.args);
351
- }
352
- handleMessageToolCallArgsDelta(payload) {
354
+ };
355
+ handleMessageToolCallArgsDelta = (payload) => {
353
356
  const currentArgs = this.toolCallArgsRawByCallId.get(payload.toolCallId) ?? "";
354
357
  const nextArgs = `${currentArgs}${payload.delta}`;
355
358
  this.toolCallArgsRawByCallId.set(payload.toolCallId, nextArgs);
@@ -359,8 +362,8 @@ var DefaultNcpAgentConversationStateManager = class {
359
362
  nextArgs,
360
363
  payload.messageId
361
364
  );
362
- }
363
- handleMessageToolCallEnd(payload) {
365
+ };
366
+ handleMessageToolCallEnd = (payload) => {
364
367
  const targetMessage = this.resolveToolCallTargetMessage(
365
368
  payload.sessionId,
366
369
  payload.toolCallId
@@ -378,8 +381,8 @@ var DefaultNcpAgentConversationStateManager = class {
378
381
  parts: nextParts,
379
382
  status: "streaming"
380
383
  });
381
- }
382
- handleMessageToolCallResult(payload) {
384
+ };
385
+ handleMessageToolCallResult = (payload) => {
383
386
  const updated = this.updateMessageContainingToolCall(
384
387
  payload.toolCallId,
385
388
  (targetMessage, existingPart) => {
@@ -412,29 +415,30 @@ var DefaultNcpAgentConversationStateManager = class {
412
415
  status: "streaming"
413
416
  });
414
417
  }
415
- }
416
- handleRunStarted(payload) {
418
+ };
419
+ handleRunStarted = (payload) => {
420
+ if (this.isSettledRunId(payload.runId)) return;
417
421
  this.setError(null);
418
- this.activeRun = {
419
- runId: payload.runId ?? null,
420
- sessionId: payload.sessionId
421
- };
422
+ this.activeRun = { runId: payload.runId ?? null, sessionId: payload.sessionId };
422
423
  this.stateVersion += 1;
423
- }
424
- handleRunFinished(_payload) {
424
+ };
425
+ handleRunFinished = (payload) => {
426
+ this.markRunAsSettled(payload.runId ?? this.activeRun?.runId ?? null);
425
427
  this.settleStreamingMessage("final");
426
428
  this.setError(null);
427
429
  this.clearActiveRun();
428
- }
429
- handleRunError(payload) {
430
+ };
431
+ handleRunError = (payload) => {
432
+ this.markRunAsSettled(payload.runId ?? this.activeRun?.runId ?? null);
430
433
  this.settleStreamingMessage("error");
431
434
  this.setError(buildRuntimeError(payload));
432
435
  this.clearActiveRun();
433
- }
434
- handleRunMetadata(payload) {
436
+ };
437
+ handleRunMetadata = (payload) => {
435
438
  const m = payload.metadata;
436
439
  if (m?.kind === "ready") {
437
440
  const ready = m;
441
+ if (this.isSettledRunId(ready.runId)) return;
438
442
  this.activeRun = {
439
443
  runId: ready.runId ?? this.activeRun?.runId ?? null,
440
444
  sessionId: ready.sessionId ?? this.activeRun?.sessionId,
@@ -442,10 +446,11 @@ var DefaultNcpAgentConversationStateManager = class {
442
446
  };
443
447
  this.stateVersion += 1;
444
448
  } else if (m?.kind === "final") {
449
+ this.markRunAsSettled(payload.runId ?? this.activeRun?.runId ?? null);
445
450
  this.clearActiveRun();
446
451
  }
447
- }
448
- handleEndpointError(payload) {
452
+ };
453
+ handleEndpointError = (payload) => {
449
454
  if (payload.code === "abort-error") {
450
455
  this.handleMessageAbort({
451
456
  sessionId: this.activeRun?.sessionId ?? this.streamingMessage?.sessionId ?? "",
@@ -456,8 +461,8 @@ var DefaultNcpAgentConversationStateManager = class {
456
461
  this.settleStreamingMessage("error");
457
462
  this.clearActiveRun();
458
463
  this.setError(payload);
459
- }
460
- applyToolCallArgs(sessionId, toolCallId, args, messageId) {
464
+ };
465
+ applyToolCallArgs = (sessionId, toolCallId, args, messageId) => {
461
466
  const targetMessage = this.resolveToolCallTargetMessage(
462
467
  sessionId,
463
468
  toolCallId,
@@ -476,8 +481,8 @@ var DefaultNcpAgentConversationStateManager = class {
476
481
  parts: nextParts,
477
482
  status: "streaming"
478
483
  });
479
- }
480
- ensureStreamingMessage(sessionId, messageId, status) {
484
+ };
485
+ ensureStreamingMessage = (sessionId, messageId, status) => {
481
486
  if (this.streamingMessage?.id === messageId) {
482
487
  if (this.streamingMessage.status === status) {
483
488
  return this.streamingMessage;
@@ -534,8 +539,8 @@ var DefaultNcpAgentConversationStateManager = class {
534
539
  };
535
540
  this.replaceStreamingMessage(nextStreamingMessage);
536
541
  return nextStreamingMessage;
537
- }
538
- resolveToolCallTargetMessage(sessionId, toolCallId, messageId) {
542
+ };
543
+ resolveToolCallTargetMessage = (sessionId, toolCallId, messageId) => {
539
544
  const preferredMessageId = messageId?.trim() || this.toolCallMessageIdByCallId.get(toolCallId) || this.streamingMessage?.id || `tool-${toolCallId}`;
540
545
  this.toolCallMessageIdByCallId.set(toolCallId, preferredMessageId);
541
546
  return this.ensureStreamingMessage(
@@ -543,8 +548,8 @@ var DefaultNcpAgentConversationStateManager = class {
543
548
  preferredMessageId,
544
549
  "streaming"
545
550
  );
546
- }
547
- updateMessageContainingToolCall(toolCallId, updater) {
551
+ };
552
+ updateMessageContainingToolCall = (toolCallId, updater) => {
548
553
  if (this.streamingMessage) {
549
554
  const part = findToolInvocationPart(
550
555
  this.streamingMessage.parts,
@@ -575,8 +580,8 @@ var DefaultNcpAgentConversationStateManager = class {
575
580
  return true;
576
581
  }
577
582
  return false;
578
- }
579
- upsertMessage(message) {
583
+ };
584
+ upsertMessage = (message) => {
580
585
  const normalizedMessage = normalizeConversationMessage(message);
581
586
  const messageIndex = this.messages.findIndex(
582
587
  (item) => item.id === normalizedMessage.id
@@ -590,15 +595,15 @@ var DefaultNcpAgentConversationStateManager = class {
590
595
  nextMessages[messageIndex] = normalizedMessage;
591
596
  this.messages = nextMessages;
592
597
  this.stateVersion += 1;
593
- }
594
- replaceStreamingMessage(nextStreamingMessage) {
598
+ };
599
+ replaceStreamingMessage = (nextStreamingMessage) => {
595
600
  if (!nextStreamingMessage && !this.streamingMessage) {
596
601
  return;
597
602
  }
598
603
  this.streamingMessage = nextStreamingMessage ? normalizeConversationMessage(nextStreamingMessage) : null;
599
604
  this.stateVersion += 1;
600
- }
601
- setError(nextError) {
605
+ };
606
+ setError = (nextError) => {
602
607
  const hasSameError = this.error?.code === nextError?.code && this.error?.message === nextError?.message && this.error?.details === nextError?.details && this.error?.cause === nextError?.cause;
603
608
  if (hasSameError) {
604
609
  return;
@@ -608,15 +613,19 @@ var DefaultNcpAgentConversationStateManager = class {
608
613
  details: nextError.details ? { ...nextError.details } : void 0
609
614
  } : null;
610
615
  this.stateVersion += 1;
611
- }
612
- clearActiveRun() {
613
- if (!this.activeRun) {
614
- return;
615
- }
616
+ };
617
+ clearActiveRun = () => {
618
+ if (!this.activeRun) return;
616
619
  this.activeRun = null;
617
620
  this.stateVersion += 1;
618
- }
619
- settleStreamingMessage(status) {
621
+ };
622
+ isSettledRunId = (runId) => {
623
+ return Boolean(runId?.trim()) && runId === this.lastSettledRunId;
624
+ };
625
+ markRunAsSettled = (runId) => {
626
+ this.lastSettledRunId = runId?.trim() || null;
627
+ };
628
+ settleStreamingMessage = (status) => {
620
629
  if (!this.streamingMessage) {
621
630
  return;
622
631
  }
@@ -631,13 +640,11 @@ var DefaultNcpAgentConversationStateManager = class {
631
640
  this.toolCallArgsRawByCallId,
632
641
  settledMessage.id
633
642
  );
634
- }
635
- notifyListeners() {
643
+ };
644
+ notifyListeners = () => {
636
645
  const snapshot = this.getSnapshot();
637
- for (const listener of this.listeners) {
638
- listener(snapshot);
639
- }
640
- }
646
+ for (const listener of this.listeners) listener(snapshot);
647
+ };
641
648
  };
642
649
 
643
650
  // src/agent/agent-client-from-server.ts
@@ -838,6 +845,7 @@ var AgentRunExecutor = class {
838
845
 
839
846
  // src/agent/agent-backend/agent-backend-session-utils.ts
840
847
  import { NcpEventType as NcpEventType4 } from "@nextclaw/ncp";
848
+ var AUTO_SESSION_LABEL_MAX_LENGTH = 64;
841
849
  function readMessages(snapshot) {
842
850
  const messages = snapshot.messages.map((message) => structuredClone(message));
843
851
  if (snapshot.streamingMessage) {
@@ -846,32 +854,83 @@ function readMessages(snapshot) {
846
854
  return messages;
847
855
  }
848
856
  function toSessionSummary(session, liveSession) {
857
+ const metadata = withAutoSessionLabel({
858
+ metadata: session.metadata ? structuredClone({
859
+ ...session.metadata,
860
+ ...liveSession?.metadata ? liveSession.metadata : {}
861
+ }) : liveSession?.metadata ? structuredClone(liveSession.metadata) : {},
862
+ messages: session.messages
863
+ });
849
864
  return {
850
865
  sessionId: session.sessionId,
851
866
  messageCount: session.messages.length,
852
867
  updatedAt: session.updatedAt,
853
868
  status: liveSession?.activeExecution ? "running" : "idle",
854
- ...session.metadata ? {
855
- metadata: structuredClone({
856
- ...session.metadata,
857
- ...liveSession?.metadata ? liveSession.metadata : {}
858
- })
859
- } : liveSession?.metadata ? { metadata: structuredClone(liveSession.metadata) } : {}
869
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
860
870
  };
861
871
  }
862
872
  function toLiveSessionSummary(session) {
863
873
  const snapshot = session.stateManager.getSnapshot();
874
+ const messages = readMessages(snapshot);
875
+ const metadata = withAutoSessionLabel({
876
+ metadata: Object.keys(session.metadata).length > 0 ? structuredClone(session.metadata) : session.activeExecution?.requestEnvelope.metadata ? structuredClone(session.activeExecution.requestEnvelope.metadata) : {},
877
+ messages
878
+ });
864
879
  return {
865
880
  sessionId: session.sessionId,
866
- messageCount: readMessages(snapshot).length,
881
+ messageCount: messages.length,
867
882
  updatedAt: now(),
868
883
  status: session.activeExecution ? "running" : "idle",
869
- ...Object.keys(session.metadata).length > 0 ? { metadata: structuredClone(session.metadata) } : session.activeExecution?.requestEnvelope.metadata ? { metadata: structuredClone(session.activeExecution.requestEnvelope.metadata) } : {}
884
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
870
885
  };
871
886
  }
872
887
  function now() {
873
888
  return (/* @__PURE__ */ new Date()).toISOString();
874
889
  }
890
+ function readOptionalString(value) {
891
+ if (typeof value !== "string") {
892
+ return null;
893
+ }
894
+ const trimmed = value.trim();
895
+ return trimmed.length > 0 ? trimmed : null;
896
+ }
897
+ function truncateLabel(value) {
898
+ const characters = Array.from(value);
899
+ if (characters.length <= AUTO_SESSION_LABEL_MAX_LENGTH) {
900
+ return value;
901
+ }
902
+ return `${characters.slice(0, AUTO_SESSION_LABEL_MAX_LENGTH).join("")}\u2026`;
903
+ }
904
+ function resolveAutoSessionLabelFromMessages(messages) {
905
+ for (const message of messages) {
906
+ if (message.role !== "user") {
907
+ continue;
908
+ }
909
+ for (const part of message.parts) {
910
+ if (part.type === "text" || part.type === "rich-text") {
911
+ const text = readOptionalString(part.text);
912
+ if (text) {
913
+ return truncateLabel(text);
914
+ }
915
+ }
916
+ }
917
+ }
918
+ return null;
919
+ }
920
+ function withAutoSessionLabel(params) {
921
+ const existingLabel = readOptionalString(params.metadata.label);
922
+ if (existingLabel) {
923
+ return params.metadata;
924
+ }
925
+ const nextLabel = resolveAutoSessionLabelFromMessages(params.messages);
926
+ if (!nextLabel) {
927
+ return params.metadata;
928
+ }
929
+ return {
930
+ ...params.metadata,
931
+ label: nextLabel
932
+ };
933
+ }
875
934
  function isTerminalEvent(event) {
876
935
  switch (event.type) {
877
936
  case NcpEventType4.MessageAbort:
@@ -897,14 +956,19 @@ function buildUpdatedSessionRecord(params) {
897
956
  };
898
957
  }
899
958
  function buildPersistedLiveSessionRecord(params) {
900
- return {
901
- sessionId: params.sessionId,
902
- messages: readMessages(params.session.stateManager.getSnapshot()),
903
- updatedAt: params.updatedAt,
959
+ const messages = readMessages(params.session.stateManager.getSnapshot());
960
+ const metadata = withAutoSessionLabel({
904
961
  metadata: {
905
962
  ...params.session.metadata ? structuredClone(params.session.metadata) : {},
906
963
  ...params.session.activeExecution?.requestEnvelope.metadata ? structuredClone(params.session.activeExecution.requestEnvelope.metadata) : {}
907
- }
964
+ },
965
+ messages
966
+ });
967
+ return {
968
+ sessionId: params.sessionId,
969
+ messages,
970
+ updatedAt: params.updatedAt,
971
+ metadata
908
972
  };
909
973
  }
910
974
 
@@ -1127,16 +1191,15 @@ var DEFAULT_SUPPORTED_PART_TYPES = ["text", "file", "source", "step-start", "rea
1127
1191
  var DefaultNcpAgentBackend = class {
1128
1192
  manifest;
1129
1193
  sessionStore;
1194
+ onSessionRunStatusChanged;
1130
1195
  sessionRegistry;
1131
1196
  executor;
1132
1197
  publisher;
1133
1198
  started = false;
1134
1199
  constructor(config) {
1135
1200
  this.sessionStore = config.sessionStore;
1136
- this.sessionRegistry = new AgentLiveSessionRegistry(
1137
- this.sessionStore,
1138
- config.createRuntime
1139
- );
1201
+ this.onSessionRunStatusChanged = config.onSessionRunStatusChanged;
1202
+ this.sessionRegistry = new AgentLiveSessionRegistry(this.sessionStore, config.createRuntime);
1140
1203
  this.executor = new AgentRunExecutor(async (sessionId) => this.persistSession(sessionId));
1141
1204
  this.publisher = new EventPublisher();
1142
1205
  this.manifest = {
@@ -1152,14 +1215,14 @@ var DefaultNcpAgentBackend = class {
1152
1215
  metadata: config.metadata
1153
1216
  };
1154
1217
  }
1155
- async start() {
1218
+ start = async () => {
1156
1219
  if (this.started) {
1157
1220
  return;
1158
1221
  }
1159
1222
  this.started = true;
1160
1223
  this.publisher.publish({ type: NcpEventType8.EndpointReady });
1161
- }
1162
- async stop() {
1224
+ };
1225
+ stop = async () => {
1163
1226
  if (!this.started) {
1164
1227
  return;
1165
1228
  }
@@ -1174,8 +1237,8 @@ var DefaultNcpAgentBackend = class {
1174
1237
  this.finishSessionExecution(session, execution);
1175
1238
  }
1176
1239
  this.sessionRegistry.clear();
1177
- }
1178
- async emit(event) {
1240
+ };
1241
+ emit = async (event) => {
1179
1242
  await this.ensureStarted();
1180
1243
  switch (event.type) {
1181
1244
  case NcpEventType8.MessageRequest:
@@ -1190,41 +1253,52 @@ var DefaultNcpAgentBackend = class {
1190
1253
  default:
1191
1254
  this.publisher.publish(event);
1192
1255
  }
1193
- }
1194
- subscribe(listener) {
1256
+ };
1257
+ subscribe = (listener) => {
1195
1258
  return this.publisher.subscribe(listener);
1196
- }
1197
- async *send(envelope, options) {
1198
- await this.ensureStarted();
1199
- const session = await this.sessionRegistry.ensureSession(
1200
- envelope.sessionId,
1201
- envelope.metadata
1202
- );
1203
- const execution = this.startSessionExecution(session, envelope, options?.signal);
1204
- try {
1205
- for await (const event of this.executor.executeRun(session, envelope, execution.controller)) {
1206
- this.publishLiveEvent(execution, event);
1207
- yield event;
1208
- }
1209
- if (execution.controller.signal.aborted && !execution.abortHandled) {
1210
- const abortEvent = await this.createAbortEvent(session.sessionId);
1211
- execution.abortHandled = true;
1212
- this.publishLiveEvent(execution, abortEvent);
1213
- yield abortEvent;
1259
+ };
1260
+ send = (envelope, options) => {
1261
+ return (async function* (self) {
1262
+ await self.ensureStarted();
1263
+ const session = await self.sessionRegistry.ensureSession(
1264
+ envelope.sessionId,
1265
+ envelope.metadata
1266
+ );
1267
+ const execution = self.startSessionExecution(
1268
+ session,
1269
+ envelope,
1270
+ options?.signal
1271
+ );
1272
+ try {
1273
+ for await (const event of self.executor.executeRun(
1274
+ session,
1275
+ envelope,
1276
+ execution.controller
1277
+ )) {
1278
+ self.publishLiveEvent(execution, event);
1279
+ yield event;
1280
+ }
1281
+ if (execution.controller.signal.aborted && !execution.abortHandled) {
1282
+ const abortEvent = await self.createAbortEvent(session.sessionId);
1283
+ execution.abortHandled = true;
1284
+ self.publishLiveEvent(execution, abortEvent);
1285
+ yield abortEvent;
1286
+ }
1287
+ } finally {
1288
+ self.finishSessionExecution(session, execution);
1289
+ await self.persistSession(session.sessionId);
1214
1290
  }
1215
- } finally {
1216
- this.finishSessionExecution(session, execution);
1217
- }
1218
- }
1219
- async abort(payload) {
1291
+ })(this);
1292
+ };
1293
+ abort = async (payload) => {
1220
1294
  await this.handleAbort(payload);
1221
- }
1295
+ };
1222
1296
  stream = (payloadOrParams, opts) => streamAgentBackendExecution({
1223
1297
  payloadOrParams,
1224
1298
  opts,
1225
1299
  sessionRegistry: this.sessionRegistry
1226
1300
  });
1227
- async listSessions() {
1301
+ listSessions = async () => {
1228
1302
  const storedSessions = await this.sessionStore.listSessions();
1229
1303
  const summaries = storedSessions.map(
1230
1304
  (session) => toSessionSummary(session, this.sessionRegistry.getSession(session.sessionId))
@@ -1236,18 +1310,18 @@ var DefaultNcpAgentBackend = class {
1236
1310
  summaries.push(toLiveSessionSummary(liveSession));
1237
1311
  }
1238
1312
  return summaries.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
1239
- }
1240
- async listSessionMessages(sessionId) {
1313
+ };
1314
+ listSessionMessages = async (sessionId) => {
1241
1315
  const liveSession = this.sessionRegistry.getSession(sessionId);
1242
1316
  if (liveSession) return readMessages(liveSession.stateManager.getSnapshot());
1243
1317
  const session = await this.sessionStore.getSession(sessionId);
1244
1318
  return session ? session.messages.map((message) => structuredClone(message)) : [];
1245
- }
1246
- async getSession(sessionId) {
1319
+ };
1320
+ getSession = async (sessionId) => {
1247
1321
  const liveSession = this.sessionRegistry.getSession(sessionId);
1248
1322
  const storedSession = await this.sessionStore.getSession(sessionId);
1249
1323
  return storedSession ? toSessionSummary(storedSession, liveSession) : liveSession ? toLiveSessionSummary(liveSession) : null;
1250
- }
1324
+ };
1251
1325
  appendMessage = async (sessionId, message) => {
1252
1326
  await this.ensureStarted();
1253
1327
  return appendAgentBackendMessage({
@@ -1272,7 +1346,7 @@ var DefaultNcpAgentBackend = class {
1272
1346
  getSession: async (nextSessionId) => this.getSession(nextSessionId)
1273
1347
  });
1274
1348
  };
1275
- async updateSession(sessionId, patch) {
1349
+ updateSession = async (sessionId, patch) => {
1276
1350
  const liveSession = this.sessionRegistry.getSession(sessionId);
1277
1351
  const storedSession = await this.sessionStore.getSession(sessionId);
1278
1352
  if (!liveSession && !storedSession) return null;
@@ -1284,8 +1358,8 @@ var DefaultNcpAgentBackend = class {
1284
1358
  updatedAt: now()
1285
1359
  }));
1286
1360
  return this.getSession(sessionId);
1287
- }
1288
- async deleteSession(sessionId) {
1361
+ };
1362
+ deleteSession = async (sessionId) => {
1289
1363
  const liveSession = this.sessionRegistry.deleteSession(sessionId);
1290
1364
  const execution = liveSession?.activeExecution;
1291
1365
  if (execution) {
@@ -1294,13 +1368,13 @@ var DefaultNcpAgentBackend = class {
1294
1368
  this.closeExecution(execution);
1295
1369
  }
1296
1370
  await this.sessionStore.deleteSession(sessionId);
1297
- }
1298
- async ensureStarted() {
1371
+ };
1372
+ ensureStarted = async () => {
1299
1373
  if (!this.started) {
1300
1374
  await this.start();
1301
1375
  }
1302
- }
1303
- startSessionExecution(session, envelope, signal) {
1376
+ };
1377
+ startSessionExecution = (session, envelope, signal) => {
1304
1378
  if (session.activeExecution && !session.activeExecution.closed) {
1305
1379
  throw new NcpErrorException(
1306
1380
  "runtime-error",
@@ -1322,39 +1396,41 @@ var DefaultNcpAgentBackend = class {
1322
1396
  closed: false
1323
1397
  };
1324
1398
  session.activeExecution = execution;
1399
+ this.onSessionRunStatusChanged?.({ sessionKey: session.sessionId, status: "running" });
1325
1400
  return execution;
1326
- }
1327
- finishSessionExecution(session, execution) {
1401
+ };
1402
+ finishSessionExecution = (session, execution) => {
1328
1403
  if (session.activeExecution === execution) {
1329
1404
  session.activeExecution = null;
1405
+ this.onSessionRunStatusChanged?.({ sessionKey: session.sessionId, status: "idle" });
1330
1406
  }
1331
1407
  this.closeExecution(execution);
1332
- }
1333
- closeExecution(execution) {
1408
+ };
1409
+ closeExecution = (execution) => {
1334
1410
  if (execution.closed) {
1335
1411
  return;
1336
1412
  }
1337
1413
  execution.closed = true;
1338
1414
  execution.publisher.close();
1339
- }
1340
- publishLiveEvent(execution, event) {
1415
+ };
1416
+ publishLiveEvent = (execution, event) => {
1341
1417
  this.publisher.publish(event);
1342
1418
  if (!execution.closed) {
1343
1419
  execution.publisher.publish(event);
1344
1420
  }
1345
- }
1346
- async handleRequest(envelope) {
1421
+ };
1422
+ handleRequest = async (envelope) => {
1347
1423
  for await (const event of this.send(envelope)) {
1348
1424
  void event;
1349
1425
  }
1350
- }
1351
- async streamToSubscribers(payload) {
1426
+ };
1427
+ streamToSubscribers = async (payload) => {
1352
1428
  const signal = new AbortController().signal;
1353
1429
  for await (const event of this.stream({ payload, signal })) {
1354
1430
  void event;
1355
1431
  }
1356
- }
1357
- async handleAbort(payload) {
1432
+ };
1433
+ handleAbort = async (payload) => {
1358
1434
  const session = this.sessionRegistry.getSession(payload.sessionId);
1359
1435
  const execution = session?.activeExecution;
1360
1436
  if (!session || !execution || execution.closed) {
@@ -1365,8 +1441,8 @@ var DefaultNcpAgentBackend = class {
1365
1441
  const abortEvent = await this.createAbortEvent(payload.sessionId, payload.messageId);
1366
1442
  this.publishLiveEvent(execution, abortEvent);
1367
1443
  this.finishSessionExecution(session, execution);
1368
- }
1369
- async createAbortEvent(sessionId, messageId) {
1444
+ };
1445
+ createAbortEvent = async (sessionId, messageId) => {
1370
1446
  const abortEvent = {
1371
1447
  type: NcpEventType8.MessageAbort,
1372
1448
  payload: {
@@ -1380,8 +1456,8 @@ var DefaultNcpAgentBackend = class {
1380
1456
  }
1381
1457
  await this.persistSession(sessionId);
1382
1458
  return abortEvent;
1383
- }
1384
- async persistSession(sessionId) {
1459
+ };
1460
+ persistSession = async (sessionId) => {
1385
1461
  const session = this.sessionRegistry.getSession(sessionId);
1386
1462
  if (!session) return;
1387
1463
  await this.sessionStore.saveSession(buildPersistedLiveSessionRecord({
@@ -1389,7 +1465,7 @@ var DefaultNcpAgentBackend = class {
1389
1465
  session,
1390
1466
  updatedAt: now()
1391
1467
  }));
1392
- }
1468
+ };
1393
1469
  };
1394
1470
 
1395
1471
  // src/agent/agent-backend/in-memory-agent-session-store.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-toolkit",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "private": false,
5
5
  "description": "Toolkit implementations built on top of the NextClaw Communication Protocol.",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "prettier": "^3.3.3",
23
23
  "tsup": "^8.3.5",
24
24
  "typescript": "^5.6.3",
25
- "vitest": "^2.1.2",
25
+ "vitest": "^4.1.2",
26
26
  "@nextclaw/ncp-agent-runtime": "0.3.2"
27
27
  },
28
28
  "scripts": {