@nextclaw/ncp-toolkit 0.2.0 → 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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NcpAgentConversationStateManager, NcpAgentConversationSnapshot, NcpAgentConversationHydrationParams, NcpEndpointEvent, NcpMessageSentPayload, NcpMessageAbortPayload, NcpTextStartPayload, NcpTextDeltaPayload, NcpTextEndPayload, NcpReasoningStartPayload, NcpReasoningDeltaPayload, NcpReasoningEndPayload, NcpToolCallStartPayload, NcpToolCallArgsPayload, NcpToolCallArgsDeltaPayload, NcpToolCallEndPayload, NcpToolCallResultPayload, NcpRunStartedPayload, NcpRunFinishedPayload, NcpRunErrorPayload, NcpRunMetadataPayload, NcpError, NcpAgentServerEndpoint, NcpAgentClientEndpoint, NcpEndpointSubscriber, NcpAgentRuntime, NcpRequestEnvelope, NcpMessage, NcpSessionApi, NcpAgentStreamProvider, NcpAgentRunApi, NcpEndpointManifest, NcpAgentRunSendOptions, NcpStreamRequestPayload, NcpAgentRunStreamOptions, NcpSessionSummary, NcpErrorCode } from '@nextclaw/ncp';
1
+ import { NcpAgentConversationStateManager, NcpAgentConversationSnapshot, NcpAgentConversationHydrationParams, NcpEndpointEvent, NcpMessageSentPayload, NcpMessageAbortPayload, NcpTextStartPayload, NcpTextDeltaPayload, NcpTextEndPayload, NcpReasoningStartPayload, NcpReasoningDeltaPayload, NcpReasoningEndPayload, NcpToolCallStartPayload, NcpToolCallArgsPayload, NcpToolCallArgsDeltaPayload, NcpToolCallEndPayload, NcpToolCallResultPayload, NcpRunStartedPayload, NcpRunFinishedPayload, NcpRunErrorPayload, NcpRunMetadataPayload, NcpError, NcpAgentServerEndpoint, NcpAgentClientEndpoint, NcpEndpointSubscriber, NcpAgentRuntime, NcpRequestEnvelope, NcpMessage, NcpSessionApi, NcpAgentStreamProvider, NcpAgentRunApi, NcpEndpointManifest, NcpAgentRunSendOptions, NcpStreamRequestPayload, NcpAgentRunStreamOptions, NcpSessionSummary, NcpSessionPatch, NcpErrorCode } from '@nextclaw/ncp';
2
2
 
3
3
  declare class DefaultNcpAgentConversationStateManager implements NcpAgentConversationStateManager {
4
4
  private messages;
@@ -38,14 +38,10 @@ declare class DefaultNcpAgentConversationStateManager implements NcpAgentConvers
38
38
  private ensureStreamingMessage;
39
39
  private resolveToolCallTargetMessage;
40
40
  private updateMessageContainingToolCall;
41
- private findToolInvocationPart;
42
- private findToolNameByCallId;
43
- private upsertToolInvocationPart;
44
41
  private upsertMessage;
45
42
  private replaceStreamingMessage;
46
43
  private setError;
47
44
  private clearActiveRun;
48
- private clearToolCallTrackingByMessageId;
49
45
  private notifyListeners;
50
46
  }
51
47
 
@@ -68,12 +64,15 @@ declare class EventPublisher {
68
64
  type RuntimeFactoryParams = {
69
65
  sessionId: string;
70
66
  stateManager: NcpAgentConversationStateManager;
67
+ sessionMetadata: Record<string, unknown>;
68
+ setSessionMetadata: (nextMetadata: Record<string, unknown>) => void;
71
69
  };
72
70
  type CreateRuntimeFn = (params: RuntimeFactoryParams) => NcpAgentRuntime;
73
71
  type AgentSessionRecord = {
74
72
  sessionId: string;
75
73
  messages: NcpMessage[];
76
74
  updatedAt: string;
75
+ metadata?: Record<string, unknown>;
77
76
  };
78
77
  type LiveSessionExecution = {
79
78
  controller: AbortController;
@@ -86,6 +85,7 @@ type LiveSessionState = {
86
85
  sessionId: string;
87
86
  runtime: NcpAgentRuntime;
88
87
  stateManager: NcpAgentConversationStateManager;
88
+ metadata: Record<string, unknown>;
89
89
  activeExecution: LiveSessionExecution | null;
90
90
  };
91
91
  interface AgentSessionStore {
@@ -127,6 +127,7 @@ declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessi
127
127
  listSessions(): Promise<NcpSessionSummary[]>;
128
128
  listSessionMessages(sessionId: string): Promise<NcpMessage[]>;
129
129
  getSession(sessionId: string): Promise<NcpSessionSummary | null>;
130
+ updateSession(sessionId: string, patch: NcpSessionPatch): Promise<NcpSessionSummary | null>;
130
131
  deleteSession(sessionId: string): Promise<void>;
131
132
  private ensureStarted;
132
133
  private startSessionExecution;
package/dist/index.js CHANGED
@@ -2,15 +2,22 @@
2
2
  import {
3
3
  NcpEventType
4
4
  } from "@nextclaw/ncp";
5
- var DEFAULT_ASSISTANT_ROLE = "assistant";
6
- var cloneMessage = (message) => {
5
+
6
+ // src/agent/agent-conversation-message-normalizer.ts
7
+ import { sanitizeAssistantReplyTags } from "@nextclaw/ncp";
8
+ function cloneConversationMessage(message) {
7
9
  return {
8
10
  ...message,
9
11
  parts: [...message.parts],
10
12
  metadata: message.metadata ? { ...message.metadata } : void 0
11
13
  };
12
- };
13
- var buildRuntimeError = (payload) => {
14
+ }
15
+ function normalizeConversationMessage(message) {
16
+ return cloneConversationMessage(sanitizeAssistantReplyTags(message));
17
+ }
18
+
19
+ // src/agent/agent-conversation-state-manager.utils.ts
20
+ function buildRuntimeError(payload) {
14
21
  const message = payload.error?.trim();
15
22
  return {
16
23
  code: "runtime-error",
@@ -22,7 +29,64 @@ var buildRuntimeError = (payload) => {
22
29
  runId: payload.runId
23
30
  }
24
31
  };
25
- };
32
+ }
33
+ function shouldPromoteStreamingMessageId(message, nextMessageId) {
34
+ if (!nextMessageId.trim()) {
35
+ return false;
36
+ }
37
+ if (message.id.startsWith("tool-")) {
38
+ return true;
39
+ }
40
+ return message.parts.some((part) => part.type === "tool-invocation");
41
+ }
42
+ function remapTrackedToolCallsToMessageId(toolCallMessageIdByCallId, fromMessageId, toMessageId) {
43
+ for (const [toolCallId, trackedMessageId] of toolCallMessageIdByCallId) {
44
+ if (trackedMessageId !== fromMessageId) {
45
+ continue;
46
+ }
47
+ toolCallMessageIdByCallId.set(toolCallId, toMessageId);
48
+ }
49
+ }
50
+ function clearToolCallTrackingByMessageId(toolCallMessageIdByCallId, toolCallArgsRawByCallId, messageId) {
51
+ for (const [toolCallId, trackedMessageId] of toolCallMessageIdByCallId) {
52
+ if (trackedMessageId !== messageId) {
53
+ continue;
54
+ }
55
+ toolCallMessageIdByCallId.delete(toolCallId);
56
+ toolCallArgsRawByCallId.delete(toolCallId);
57
+ }
58
+ }
59
+ function findToolInvocationPart(parts, toolCallId) {
60
+ for (let index = parts.length - 1; index >= 0; index -= 1) {
61
+ const part = parts[index];
62
+ if (part.type === "tool-invocation" && part.toolCallId === toolCallId) {
63
+ return part;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ function findToolNameByCallId(parts, toolCallId) {
69
+ const part = findToolInvocationPart(parts, toolCallId);
70
+ return part?.toolName ?? null;
71
+ }
72
+ function upsertToolInvocationPart(parts, toolPart) {
73
+ const nextParts = [...parts];
74
+ for (let index = nextParts.length - 1; index >= 0; index -= 1) {
75
+ const part = nextParts[index];
76
+ if (part.type === "tool-invocation" && part.toolCallId === toolPart.toolCallId) {
77
+ nextParts[index] = {
78
+ ...part,
79
+ ...toolPart
80
+ };
81
+ return nextParts;
82
+ }
83
+ }
84
+ nextParts.push(toolPart);
85
+ return nextParts;
86
+ }
87
+
88
+ // src/agent/agent-conversation-state-manager.ts
89
+ var DEFAULT_ASSISTANT_ROLE = "assistant";
26
90
  var DefaultNcpAgentConversationStateManager = class {
27
91
  messages = [];
28
92
  streamingMessage = null;
@@ -39,8 +103,8 @@ var DefaultNcpAgentConversationStateManager = class {
39
103
  return this.snapshotCache;
40
104
  }
41
105
  const snapshot = {
42
- messages: this.messages.map((message) => cloneMessage(message)),
43
- streamingMessage: this.streamingMessage ? cloneMessage(this.streamingMessage) : null,
106
+ messages: this.messages.map((message) => cloneConversationMessage(message)),
107
+ streamingMessage: this.streamingMessage ? cloneConversationMessage(this.streamingMessage) : null,
44
108
  error: this.error ? { ...this.error, details: this.error.details ? { ...this.error.details } : void 0 } : null,
45
109
  activeRun: this.activeRun ? { ...this.activeRun } : null
46
110
  };
@@ -66,7 +130,7 @@ var DefaultNcpAgentConversationStateManager = class {
66
130
  this.notifyListeners();
67
131
  }
68
132
  hydrate(payload) {
69
- this.messages = payload.messages.map((message) => cloneMessage(message));
133
+ this.messages = payload.messages.map((message) => normalizeConversationMessage(message));
70
134
  this.streamingMessage = null;
71
135
  this.error = null;
72
136
  this.activeRun = payload.activeRun ? {
@@ -159,9 +223,17 @@ var DefaultNcpAgentConversationStateManager = class {
159
223
  });
160
224
  this.replaceStreamingMessage(null);
161
225
  if (targetMessageId) {
162
- this.clearToolCallTrackingByMessageId(targetMessageId);
226
+ clearToolCallTrackingByMessageId(
227
+ this.toolCallMessageIdByCallId,
228
+ this.toolCallArgsRawByCallId,
229
+ targetMessageId
230
+ );
163
231
  } else {
164
- this.clearToolCallTrackingByMessageId(streamingMessageId);
232
+ clearToolCallTrackingByMessageId(
233
+ this.toolCallMessageIdByCallId,
234
+ this.toolCallArgsRawByCallId,
235
+ streamingMessageId
236
+ );
165
237
  }
166
238
  }
167
239
  }
@@ -238,7 +310,7 @@ var DefaultNcpAgentConversationStateManager = class {
238
310
  payload.messageId
239
311
  );
240
312
  this.toolCallArgsRawByCallId.set(payload.toolCallId, "");
241
- const nextParts = this.upsertToolInvocationPart(targetMessage.parts, {
313
+ const nextParts = upsertToolInvocationPart(targetMessage.parts, {
242
314
  type: "tool-invocation",
243
315
  toolCallId: payload.toolCallId,
244
316
  toolName: payload.toolName,
@@ -265,10 +337,10 @@ var DefaultNcpAgentConversationStateManager = class {
265
337
  handleMessageToolCallEnd(payload) {
266
338
  const targetMessage = this.resolveToolCallTargetMessage(payload.sessionId, payload.toolCallId);
267
339
  const args = this.toolCallArgsRawByCallId.get(payload.toolCallId) ?? "";
268
- const nextParts = this.upsertToolInvocationPart(targetMessage.parts, {
340
+ const nextParts = upsertToolInvocationPart(targetMessage.parts, {
269
341
  type: "tool-invocation",
270
342
  toolCallId: payload.toolCallId,
271
- toolName: this.findToolNameByCallId(targetMessage.parts, payload.toolCallId) ?? "unknown",
343
+ toolName: findToolNameByCallId(targetMessage.parts, payload.toolCallId) ?? "unknown",
272
344
  state: "call",
273
345
  args
274
346
  });
@@ -288,11 +360,11 @@ var DefaultNcpAgentConversationStateManager = class {
288
360
  args: existingPart.args,
289
361
  result: payload.content
290
362
  };
291
- return this.upsertToolInvocationPart(targetMessage.parts, mergedPart);
363
+ return upsertToolInvocationPart(targetMessage.parts, mergedPart);
292
364
  });
293
365
  if (!updated) {
294
366
  const fallbackMessage = this.resolveToolCallTargetMessage(payload.sessionId, payload.toolCallId);
295
- const nextParts = this.upsertToolInvocationPart(fallbackMessage.parts, {
367
+ const nextParts = upsertToolInvocationPart(fallbackMessage.parts, {
296
368
  type: "tool-invocation",
297
369
  toolCallId: payload.toolCallId,
298
370
  toolName: "unknown",
@@ -322,7 +394,11 @@ var DefaultNcpAgentConversationStateManager = class {
322
394
  };
323
395
  this.upsertMessage(finalizedMessage);
324
396
  this.replaceStreamingMessage(null);
325
- this.clearToolCallTrackingByMessageId(finalizedMessage.id);
397
+ clearToolCallTrackingByMessageId(
398
+ this.toolCallMessageIdByCallId,
399
+ this.toolCallArgsRawByCallId,
400
+ finalizedMessage.id
401
+ );
326
402
  }
327
403
  this.setError(null);
328
404
  this.clearActiveRun();
@@ -335,7 +411,11 @@ var DefaultNcpAgentConversationStateManager = class {
335
411
  };
336
412
  this.upsertMessage(failedMessage);
337
413
  this.replaceStreamingMessage(null);
338
- this.clearToolCallTrackingByMessageId(failedMessage.id);
414
+ clearToolCallTrackingByMessageId(
415
+ this.toolCallMessageIdByCallId,
416
+ this.toolCallArgsRawByCallId,
417
+ failedMessage.id
418
+ );
339
419
  }
340
420
  this.setError(buildRuntimeError(payload));
341
421
  this.clearActiveRun();
@@ -359,8 +439,8 @@ var DefaultNcpAgentConversationStateManager = class {
359
439
  }
360
440
  applyToolCallArgs(sessionId, toolCallId, args, messageId) {
361
441
  const targetMessage = this.resolveToolCallTargetMessage(sessionId, toolCallId, messageId);
362
- const toolName = this.findToolNameByCallId(targetMessage.parts, toolCallId) ?? "unknown";
363
- const nextParts = this.upsertToolInvocationPart(targetMessage.parts, {
442
+ const toolName = findToolNameByCallId(targetMessage.parts, toolCallId) ?? "unknown";
443
+ const nextParts = upsertToolInvocationPart(targetMessage.parts, {
364
444
  type: "tool-invocation",
365
445
  toolCallId,
366
446
  toolName,
@@ -387,7 +467,7 @@ var DefaultNcpAgentConversationStateManager = class {
387
467
  }
388
468
  const messageIndex = this.messages.findIndex((message) => message.id === messageId);
389
469
  if (messageIndex >= 0) {
390
- const existingMessage = cloneMessage(this.messages[messageIndex]);
470
+ const existingMessage = cloneConversationMessage(this.messages[messageIndex]);
391
471
  const nextMessages = [...this.messages];
392
472
  nextMessages.splice(messageIndex, 1);
393
473
  this.messages = nextMessages;
@@ -400,6 +480,22 @@ var DefaultNcpAgentConversationStateManager = class {
400
480
  this.replaceStreamingMessage(nextStreamingMessage2);
401
481
  return nextStreamingMessage2;
402
482
  }
483
+ const existingStreamingMessage = this.streamingMessage;
484
+ if (existingStreamingMessage && existingStreamingMessage.id !== messageId && existingStreamingMessage.sessionId === sessionId && shouldPromoteStreamingMessageId(existingStreamingMessage, messageId)) {
485
+ const nextStreamingMessage2 = {
486
+ ...existingStreamingMessage,
487
+ id: messageId,
488
+ sessionId,
489
+ status
490
+ };
491
+ remapTrackedToolCallsToMessageId(
492
+ this.toolCallMessageIdByCallId,
493
+ existingStreamingMessage.id,
494
+ messageId
495
+ );
496
+ this.replaceStreamingMessage(nextStreamingMessage2);
497
+ return nextStreamingMessage2;
498
+ }
403
499
  const nextStreamingMessage = {
404
500
  id: messageId,
405
501
  sessionId,
@@ -418,7 +514,7 @@ var DefaultNcpAgentConversationStateManager = class {
418
514
  }
419
515
  updateMessageContainingToolCall(toolCallId, updater) {
420
516
  if (this.streamingMessage) {
421
- const part = this.findToolInvocationPart(this.streamingMessage.parts, toolCallId);
517
+ const part = findToolInvocationPart(this.streamingMessage.parts, toolCallId);
422
518
  if (part) {
423
519
  const nextParts = updater(this.streamingMessage, part);
424
520
  this.replaceStreamingMessage({ ...this.streamingMessage, parts: nextParts });
@@ -427,7 +523,7 @@ var DefaultNcpAgentConversationStateManager = class {
427
523
  }
428
524
  for (let index = this.messages.length - 1; index >= 0; index -= 1) {
429
525
  const candidateMessage = this.messages[index];
430
- const part = this.findToolInvocationPart(candidateMessage.parts, toolCallId);
526
+ const part = findToolInvocationPart(candidateMessage.parts, toolCallId);
431
527
  if (!part) {
432
528
  continue;
433
529
  }
@@ -442,36 +538,8 @@ var DefaultNcpAgentConversationStateManager = class {
442
538
  }
443
539
  return false;
444
540
  }
445
- findToolInvocationPart(parts, toolCallId) {
446
- for (let index = parts.length - 1; index >= 0; index -= 1) {
447
- const part = parts[index];
448
- if (part.type === "tool-invocation" && part.toolCallId === toolCallId) {
449
- return part;
450
- }
451
- }
452
- return null;
453
- }
454
- findToolNameByCallId(parts, toolCallId) {
455
- const part = this.findToolInvocationPart(parts, toolCallId);
456
- return part?.toolName ?? null;
457
- }
458
- upsertToolInvocationPart(parts, toolPart) {
459
- const nextParts = [...parts];
460
- for (let index = nextParts.length - 1; index >= 0; index -= 1) {
461
- const part = nextParts[index];
462
- if (part.type === "tool-invocation" && part.toolCallId === toolPart.toolCallId) {
463
- nextParts[index] = {
464
- ...part,
465
- ...toolPart
466
- };
467
- return nextParts;
468
- }
469
- }
470
- nextParts.push(toolPart);
471
- return nextParts;
472
- }
473
541
  upsertMessage(message) {
474
- const normalizedMessage = cloneMessage(message);
542
+ const normalizedMessage = normalizeConversationMessage(message);
475
543
  const messageIndex = this.messages.findIndex((item) => item.id === normalizedMessage.id);
476
544
  if (messageIndex < 0) {
477
545
  this.messages = [...this.messages, normalizedMessage];
@@ -487,7 +555,7 @@ var DefaultNcpAgentConversationStateManager = class {
487
555
  if (!nextStreamingMessage && !this.streamingMessage) {
488
556
  return;
489
557
  }
490
- this.streamingMessage = nextStreamingMessage ? cloneMessage(nextStreamingMessage) : null;
558
+ this.streamingMessage = nextStreamingMessage ? normalizeConversationMessage(nextStreamingMessage) : null;
491
559
  this.stateVersion += 1;
492
560
  }
493
561
  setError(nextError) {
@@ -505,15 +573,6 @@ var DefaultNcpAgentConversationStateManager = class {
505
573
  this.activeRun = null;
506
574
  this.stateVersion += 1;
507
575
  }
508
- clearToolCallTrackingByMessageId(messageId) {
509
- for (const [toolCallId, trackedMessageId] of this.toolCallMessageIdByCallId) {
510
- if (trackedMessageId !== messageId) {
511
- continue;
512
- }
513
- this.toolCallMessageIdByCallId.delete(toolCallId);
514
- this.toolCallArgsRawByCallId.delete(toolCallId);
515
- }
516
- }
517
576
  notifyListeners() {
518
577
  const snapshot = this.getSnapshot();
519
578
  for (const listener of this.listeners) {
@@ -574,7 +633,7 @@ async function consume(events) {
574
633
 
575
634
  // src/agent/agent-backend/agent-backend.ts
576
635
  import {
577
- NcpEventType as NcpEventType4
636
+ NcpEventType as NcpEventType5
578
637
  } from "@nextclaw/ncp";
579
638
 
580
639
  // src/errors/ncp-error-exception.ts
@@ -596,9 +655,15 @@ var AgentLiveSessionRegistry = class {
596
655
  this.createRuntime = createRuntime;
597
656
  }
598
657
  sessions = /* @__PURE__ */ new Map();
599
- async ensureSession(sessionId) {
658
+ async ensureSession(sessionId, initialMetadata) {
600
659
  const existing = this.sessions.get(sessionId);
601
660
  if (existing) {
661
+ if (initialMetadata && Object.keys(initialMetadata).length > 0) {
662
+ existing.metadata = {
663
+ ...existing.metadata,
664
+ ...structuredClone(initialMetadata)
665
+ };
666
+ }
602
667
  return existing;
603
668
  }
604
669
  const storedSession = await this.sessionStore.getSession(sessionId);
@@ -607,12 +672,27 @@ var AgentLiveSessionRegistry = class {
607
672
  sessionId,
608
673
  messages: cloneMessages(storedSession?.messages ?? [])
609
674
  });
675
+ const sessionMetadata = {
676
+ ...storedSession?.metadata ? structuredClone(storedSession.metadata) : {},
677
+ ...initialMetadata ? structuredClone(initialMetadata) : {}
678
+ };
610
679
  const session = {
611
680
  sessionId,
612
681
  stateManager,
613
- runtime: this.createRuntime({ sessionId, stateManager }),
682
+ metadata: sessionMetadata,
683
+ runtime: null,
614
684
  activeExecution: null
615
685
  };
686
+ session.runtime = this.createRuntime({
687
+ sessionId,
688
+ stateManager,
689
+ sessionMetadata,
690
+ setSessionMetadata: (nextMetadata) => {
691
+ session.metadata = {
692
+ ...structuredClone(nextMetadata)
693
+ };
694
+ }
695
+ });
616
696
  this.sessions.set(sessionId, session);
617
697
  return session;
618
698
  }
@@ -652,21 +732,19 @@ var AgentRunExecutor = class {
652
732
  metadata: envelope.metadata
653
733
  }
654
734
  };
655
- let messageSentPublished = false;
735
+ await session.stateManager.dispatch(messageSent);
736
+ await this.persistSession(envelope.sessionId);
737
+ yield structuredClone(messageSent);
656
738
  try {
657
739
  for await (const event of session.runtime.run(
658
740
  {
659
741
  sessionId: envelope.sessionId,
660
742
  messages: [envelope.message],
661
- correlationId: envelope.correlationId
743
+ correlationId: envelope.correlationId,
744
+ metadata: envelope.metadata
662
745
  },
663
746
  { signal: controller.signal }
664
747
  )) {
665
- if (!messageSentPublished) {
666
- messageSentPublished = true;
667
- await this.persistSession(envelope.sessionId);
668
- yield structuredClone(messageSent);
669
- }
670
748
  await this.persistSession(envelope.sessionId);
671
749
  yield structuredClone(event);
672
750
  }
@@ -753,6 +831,78 @@ function createAsyncQueue() {
753
831
  };
754
832
  }
755
833
 
834
+ // src/agent/agent-backend/agent-backend-session-utils.ts
835
+ import { NcpEventType as NcpEventType4 } from "@nextclaw/ncp";
836
+ function readMessages(snapshot) {
837
+ const messages = snapshot.messages.map((message) => structuredClone(message));
838
+ if (snapshot.streamingMessage) {
839
+ messages.push(structuredClone(snapshot.streamingMessage));
840
+ }
841
+ return messages;
842
+ }
843
+ function toSessionSummary(session, liveSession) {
844
+ return {
845
+ sessionId: session.sessionId,
846
+ messageCount: session.messages.length,
847
+ updatedAt: session.updatedAt,
848
+ status: liveSession?.activeExecution ? "running" : "idle",
849
+ ...session.metadata ? {
850
+ metadata: structuredClone({
851
+ ...session.metadata,
852
+ ...liveSession?.metadata ? liveSession.metadata : {}
853
+ })
854
+ } : liveSession?.metadata ? { metadata: structuredClone(liveSession.metadata) } : {}
855
+ };
856
+ }
857
+ function toLiveSessionSummary(session) {
858
+ const snapshot = session.stateManager.getSnapshot();
859
+ return {
860
+ sessionId: session.sessionId,
861
+ messageCount: readMessages(snapshot).length,
862
+ updatedAt: now(),
863
+ status: session.activeExecution ? "running" : "idle",
864
+ ...Object.keys(session.metadata).length > 0 ? { metadata: structuredClone(session.metadata) } : session.activeExecution?.requestEnvelope.metadata ? { metadata: structuredClone(session.activeExecution.requestEnvelope.metadata) } : {}
865
+ };
866
+ }
867
+ function now() {
868
+ return (/* @__PURE__ */ new Date()).toISOString();
869
+ }
870
+ function isTerminalEvent(event) {
871
+ switch (event.type) {
872
+ case NcpEventType4.MessageAbort:
873
+ case NcpEventType4.RunFinished:
874
+ case NcpEventType4.RunError:
875
+ return true;
876
+ default:
877
+ return false;
878
+ }
879
+ }
880
+
881
+ // src/agent/agent-backend/agent-backend-session-persistence.ts
882
+ function buildUpdatedSessionRecord(params) {
883
+ const nextMetadata = params.patch.metadata === null ? {} : params.patch.metadata ? structuredClone(params.patch.metadata) : structuredClone(params.liveSession?.metadata ?? params.storedSession?.metadata ?? {});
884
+ if (params.liveSession) {
885
+ params.liveSession.metadata = structuredClone(nextMetadata);
886
+ }
887
+ return {
888
+ sessionId: params.sessionId,
889
+ messages: params.liveSession ? readMessages(params.liveSession.stateManager.getSnapshot()) : params.storedSession?.messages.map((message) => structuredClone(message)) ?? [],
890
+ updatedAt: params.updatedAt,
891
+ metadata: nextMetadata
892
+ };
893
+ }
894
+ function buildPersistedLiveSessionRecord(params) {
895
+ return {
896
+ sessionId: params.sessionId,
897
+ messages: readMessages(params.session.stateManager.getSnapshot()),
898
+ updatedAt: params.updatedAt,
899
+ metadata: {
900
+ ...params.session.metadata ? structuredClone(params.session.metadata) : {},
901
+ ...params.session.activeExecution?.requestEnvelope.metadata ? structuredClone(params.session.activeExecution.requestEnvelope.metadata) : {}
902
+ }
903
+ };
904
+ }
905
+
756
906
  // src/agent/agent-backend/event-publisher.ts
757
907
  var EventPublisher = class {
758
908
  listeners = /* @__PURE__ */ new Set();
@@ -844,7 +994,7 @@ var DefaultNcpAgentBackend = class {
844
994
  return;
845
995
  }
846
996
  this.started = true;
847
- this.publisher.publish({ type: NcpEventType4.EndpointReady });
997
+ this.publisher.publish({ type: NcpEventType5.EndpointReady });
848
998
  }
849
999
  async stop() {
850
1000
  if (!this.started) {
@@ -865,13 +1015,13 @@ var DefaultNcpAgentBackend = class {
865
1015
  async emit(event) {
866
1016
  await this.ensureStarted();
867
1017
  switch (event.type) {
868
- case NcpEventType4.MessageRequest:
1018
+ case NcpEventType5.MessageRequest:
869
1019
  await this.handleRequest(event.payload);
870
1020
  return;
871
- case NcpEventType4.MessageStreamRequest:
1021
+ case NcpEventType5.MessageStreamRequest:
872
1022
  await this.streamToSubscribers(event.payload);
873
1023
  return;
874
- case NcpEventType4.MessageAbort:
1024
+ case NcpEventType5.MessageAbort:
875
1025
  await this.handleAbort(event.payload);
876
1026
  return;
877
1027
  default:
@@ -883,7 +1033,10 @@ var DefaultNcpAgentBackend = class {
883
1033
  }
884
1034
  async *send(envelope, options) {
885
1035
  await this.ensureStarted();
886
- const session = await this.sessionRegistry.ensureSession(envelope.sessionId);
1036
+ const session = await this.sessionRegistry.ensureSession(
1037
+ envelope.sessionId,
1038
+ envelope.metadata
1039
+ );
887
1040
  const execution = this.startSessionExecution(session, envelope, options?.signal);
888
1041
  try {
889
1042
  for await (const event of this.executor.executeRun(session, envelope, execution.controller)) {
@@ -954,22 +1107,27 @@ var DefaultNcpAgentBackend = class {
954
1107
  }
955
1108
  async listSessionMessages(sessionId) {
956
1109
  const liveSession = this.sessionRegistry.getSession(sessionId);
957
- if (liveSession) {
958
- return readMessages(liveSession.stateManager.getSnapshot());
959
- }
1110
+ if (liveSession) return readMessages(liveSession.stateManager.getSnapshot());
960
1111
  const session = await this.sessionStore.getSession(sessionId);
961
1112
  return session ? session.messages.map((message) => structuredClone(message)) : [];
962
1113
  }
963
1114
  async getSession(sessionId) {
964
1115
  const liveSession = this.sessionRegistry.getSession(sessionId);
965
1116
  const storedSession = await this.sessionStore.getSession(sessionId);
966
- if (storedSession) {
967
- return toSessionSummary(storedSession, liveSession);
968
- }
969
- if (liveSession) {
970
- return toLiveSessionSummary(liveSession);
971
- }
972
- return null;
1117
+ return storedSession ? toSessionSummary(storedSession, liveSession) : liveSession ? toLiveSessionSummary(liveSession) : null;
1118
+ }
1119
+ async updateSession(sessionId, patch) {
1120
+ const liveSession = this.sessionRegistry.getSession(sessionId);
1121
+ const storedSession = await this.sessionStore.getSession(sessionId);
1122
+ if (!liveSession && !storedSession) return null;
1123
+ await this.sessionStore.saveSession(buildUpdatedSessionRecord({
1124
+ sessionId,
1125
+ patch,
1126
+ liveSession,
1127
+ storedSession,
1128
+ updatedAt: now()
1129
+ }));
1130
+ return this.getSession(sessionId);
973
1131
  }
974
1132
  async deleteSession(sessionId) {
975
1133
  const liveSession = this.sessionRegistry.deleteSession(sessionId);
@@ -1054,7 +1212,7 @@ var DefaultNcpAgentBackend = class {
1054
1212
  }
1055
1213
  async createAbortEvent(sessionId, messageId) {
1056
1214
  const abortEvent = {
1057
- type: NcpEventType4.MessageAbort,
1215
+ type: NcpEventType5.MessageAbort,
1058
1216
  payload: {
1059
1217
  sessionId,
1060
1218
  ...messageId ? { messageId } : {}
@@ -1069,54 +1227,14 @@ var DefaultNcpAgentBackend = class {
1069
1227
  }
1070
1228
  async persistSession(sessionId) {
1071
1229
  const session = this.sessionRegistry.getSession(sessionId);
1072
- if (!session) {
1073
- return;
1074
- }
1075
- const snapshot = session.stateManager.getSnapshot();
1076
- await this.sessionStore.saveSession({
1230
+ if (!session) return;
1231
+ await this.sessionStore.saveSession(buildPersistedLiveSessionRecord({
1077
1232
  sessionId,
1078
- messages: readMessages(snapshot),
1233
+ session,
1079
1234
  updatedAt: now()
1080
- });
1235
+ }));
1081
1236
  }
1082
1237
  };
1083
- function readMessages(snapshot) {
1084
- const messages = snapshot.messages.map((message) => structuredClone(message));
1085
- if (snapshot.streamingMessage) {
1086
- messages.push(structuredClone(snapshot.streamingMessage));
1087
- }
1088
- return messages;
1089
- }
1090
- function toSessionSummary(session, liveSession) {
1091
- return {
1092
- sessionId: session.sessionId,
1093
- messageCount: session.messages.length,
1094
- updatedAt: session.updatedAt,
1095
- status: liveSession?.activeExecution ? "running" : "idle"
1096
- };
1097
- }
1098
- function toLiveSessionSummary(session) {
1099
- const snapshot = session.stateManager.getSnapshot();
1100
- return {
1101
- sessionId: session.sessionId,
1102
- messageCount: readMessages(snapshot).length,
1103
- updatedAt: now(),
1104
- status: session.activeExecution ? "running" : "idle"
1105
- };
1106
- }
1107
- function now() {
1108
- return (/* @__PURE__ */ new Date()).toISOString();
1109
- }
1110
- function isTerminalEvent(event) {
1111
- switch (event.type) {
1112
- case NcpEventType4.MessageAbort:
1113
- case NcpEventType4.RunFinished:
1114
- case NcpEventType4.RunError:
1115
- return true;
1116
- default:
1117
- return false;
1118
- }
1119
- }
1120
1238
 
1121
1239
  // src/agent/agent-backend/in-memory-agent-session-store.ts
1122
1240
  var InMemoryAgentSessionStore = class {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-toolkit",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Toolkit implementations built on top of the NextClaw Communication Protocol.",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@nextclaw/ncp": "0.2.0"
18
+ "@nextclaw/ncp": "0.3.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.17.6",
@@ -23,7 +23,7 @@
23
23
  "tsup": "^8.3.5",
24
24
  "typescript": "^5.6.3",
25
25
  "vitest": "^2.1.2",
26
- "@nextclaw/ncp-agent-runtime": "0.1.1"
26
+ "@nextclaw/ncp-agent-runtime": "0.2.0"
27
27
  },
28
28
  "scripts": {
29
29
  "build": "tsup src/index.ts --format esm --dts --out-dir dist",