@nextclaw/ncp-toolkit 0.5.13 → 0.5.15

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
@@ -72,7 +72,7 @@ declare class EventPublisher {
72
72
  close(): void;
73
73
  }
74
74
  //#endregion
75
- //#region src/agent/agent-backend/agent-backend-types.d.ts
75
+ //#region src/agent/agent-backend/agent-backend.types.d.ts
76
76
  type RuntimeFactoryParams = {
77
77
  sessionId: string;
78
78
  agentId?: string;
@@ -87,6 +87,14 @@ type AgentSessionRecord = {
87
87
  sessionId: string;
88
88
  agentId?: string;
89
89
  messages: NcpMessage[];
90
+ createdAt?: string;
91
+ updatedAt: string;
92
+ metadata?: Record<string, unknown>;
93
+ };
94
+ type AgentSessionEventRecord = {
95
+ sessionId: string;
96
+ agentId?: string;
97
+ createdAt?: string;
90
98
  updatedAt: string;
91
99
  metadata?: Record<string, unknown>;
92
100
  };
@@ -99,6 +107,7 @@ type LiveSessionExecution = {
99
107
  type LiveSessionState = {
100
108
  sessionId: string;
101
109
  agentId?: string;
110
+ createdAt: string;
102
111
  runtime: NcpAgentRuntime;
103
112
  stateManager: NcpAgentConversationStateManager;
104
113
  metadata: Record<string, unknown>;
@@ -108,19 +117,27 @@ type LiveSessionState = {
108
117
  interface AgentSessionStore {
109
118
  getSession(sessionId: string): Promise<AgentSessionRecord | null>;
110
119
  listSessions(): Promise<AgentSessionRecord[]>;
120
+ getSessionSummary?(sessionId: string): Promise<NcpSessionSummary | null>;
121
+ listSessionSummaries?(): Promise<NcpSessionSummary[]>;
122
+ listSessionMessages?(sessionId: string): Promise<NcpMessage[]>;
123
+ appendSessionEvent?(params: {
124
+ session: AgentSessionEventRecord;
125
+ event: NcpEndpointEvent;
126
+ updatedAt: string;
127
+ }): Promise<void>;
111
128
  saveSession(session: AgentSessionRecord): Promise<void>;
112
129
  replaceSession(session: AgentSessionRecord): Promise<void>;
113
130
  deleteSession(sessionId: string): Promise<AgentSessionRecord | null>;
114
131
  }
115
132
  //#endregion
116
- //#region src/agent/agent-backend/agent-backend-session-utils.d.ts
133
+ //#region src/agent/agent-backend/agent-backend-session.utils.d.ts
117
134
  type SessionContextWindowResolver = (params: {
118
135
  messages: readonly NcpMessage[];
119
136
  metadata: Record<string, unknown>;
120
137
  sessionId: string;
121
138
  }) => Record<string, unknown> | null;
122
139
  //#endregion
123
- //#region src/agent/agent-backend/agent-backend.d.ts
140
+ //#region src/agent/agent-backend/agent-backend.service.d.ts
124
141
  type DefaultNcpAgentBackendConfig = {
125
142
  createRuntime: CreateRuntimeFn;
126
143
  sessionStore: AgentSessionStore;
@@ -159,6 +176,7 @@ declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessi
159
176
  signal: AbortSignal;
160
177
  }, opts?: NcpAgentRunStreamOptions) => AsyncIterable<NcpEndpointEvent>;
161
178
  listSessions: () => Promise<NcpSessionSummary[]>;
179
+ isLiveSessionRunning: (sessionId: string) => boolean;
162
180
  listSessionMessages: (sessionId: string) => Promise<NcpMessage[]>;
163
181
  getSession: (sessionId: string) => Promise<NcpSessionSummary | null>;
164
182
  appendMessage: (sessionId: string, message: NcpMessage) => Promise<NcpSessionSummary | null>;
@@ -169,16 +187,15 @@ declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessi
169
187
  private startSessionExecution;
170
188
  private finishSessionExecution;
171
189
  private handleAbort;
172
- private persistSession;
173
190
  }
174
191
  //#endregion
175
- //#region src/agent/agent-backend/agent-run-executor.d.ts
192
+ //#region src/agent/agent-backend/agent-run-executor.service.d.ts
176
193
  declare class AgentRunExecutor {
177
- executeRun(session: LiveSessionState, envelope: NcpRequestEnvelope, controller: AbortController): AsyncGenerator<NcpEndpointEvent>;
194
+ executeRun: (session: LiveSessionState, envelope: NcpRequestEnvelope, controller: AbortController) => AsyncGenerator<NcpEndpointEvent>;
178
195
  private publishFailure;
179
196
  }
180
197
  //#endregion
181
- //#region src/agent/agent-backend/in-memory-agent-session-store.d.ts
198
+ //#region src/agent/agent-backend/in-memory-agent-session.store.d.ts
182
199
  declare class InMemoryAgentSessionStore implements AgentSessionStore {
183
200
  private readonly sessions;
184
201
  getSession: (sessionId: string) => Promise<AgentSessionRecord | null>;
@@ -224,4 +241,4 @@ declare class NcpReplyConsumer {
224
241
  consume: (input: NcpReplyInput) => Promise<void>;
225
242
  }
226
243
  //#endregion
227
- export { AgentRunExecutor, type AgentSessionRecord, type AgentSessionStore, Chat, ChatTarget, type CreateRuntimeFn, DefaultNcpAgentBackend, type DefaultNcpAgentBackendConfig, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, type LiveSessionExecution, type LiveSessionState, NcpErrorException, NcpEventStream, NcpReplyConsumer, NcpReplyInput, type RuntimeFactoryParams, createAgentClientFromServer };
244
+ export { AgentRunExecutor, type AgentSessionEventRecord, type AgentSessionRecord, type AgentSessionStore, Chat, ChatTarget, type CreateRuntimeFn, DefaultNcpAgentBackend, type DefaultNcpAgentBackendConfig, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, type LiveSessionExecution, type LiveSessionState, NcpErrorException, NcpEventStream, NcpReplyConsumer, NcpReplyInput, type RuntimeFactoryParams, createAgentClientFromServer };
package/dist/index.js CHANGED
@@ -574,6 +574,9 @@ var DefaultNcpAgentConversationStateManager = class {
574
574
  };
575
575
  //#endregion
576
576
  //#region src/agent/agent-client-from-server.ts
577
+ function assertMaterializedEnvelope(envelope) {
578
+ if (!envelope.sessionId?.trim() || !envelope.message.sessionId?.trim()) throw new Error("NCP request envelope must be materialized before server dispatch.");
579
+ }
577
580
  /**
578
581
  * Creates an NcpAgentClientEndpoint that forwards to an in-process NcpAgentServerEndpoint.
579
582
  * Use when the agent runs in-process and you need to pass a client endpoint to the HTTP server.
@@ -592,6 +595,7 @@ function createAgentClientFromServer(server) {
592
595
  async emit(event) {
593
596
  switch (event.type) {
594
597
  case NcpEventType.MessageRequest:
598
+ assertMaterializedEnvelope(event.payload);
595
599
  await consume(server.send(event.payload));
596
600
  return;
597
601
  case NcpEventType.MessageStreamRequest:
@@ -607,6 +611,7 @@ function createAgentClientFromServer(server) {
607
611
  return server.subscribe(listener);
608
612
  },
609
613
  async send(envelope) {
614
+ assertMaterializedEnvelope(envelope);
610
615
  await consume(server.send(envelope));
611
616
  },
612
617
  async stream(payload) {
@@ -656,7 +661,7 @@ var EventPublisher = class {
656
661
  }
657
662
  };
658
663
  //#endregion
659
- //#region src/agent/agent-backend/agent-live-session-registry.ts
664
+ //#region src/agent/agent-backend/agent-live-session-registry.service.ts
660
665
  function readOptionalAgentId$2(value) {
661
666
  if (typeof value !== "string") return;
662
667
  const trimmed = value.trim().toLowerCase();
@@ -695,6 +700,7 @@ var AgentLiveSessionRegistry = class {
695
700
  const session = {
696
701
  sessionId,
697
702
  ...sessionAgentId ? { agentId: sessionAgentId } : {},
703
+ createdAt: storedSession?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
698
704
  stateManager,
699
705
  metadata: sessionMetadata,
700
706
  runtime: null,
@@ -747,7 +753,7 @@ var NcpErrorException = class extends Error {
747
753
  }
748
754
  };
749
755
  //#endregion
750
- //#region src/agent/agent-backend/agent-backend-execution-utils.ts
756
+ //#region src/agent/agent-backend/agent-backend-execution.utils.ts
751
757
  function startAgentBackendSessionExecution(params) {
752
758
  const { session, envelope, signal, onStatusChanged } = params;
753
759
  if (session.activeExecution && !session.activeExecution.closed) throw new NcpErrorException("runtime-error", `Session ${session.sessionId} already has an active execution.`, { sessionId: session.sessionId });
@@ -775,22 +781,18 @@ function finishAgentBackendSessionExecution(params) {
775
781
  status: "idle"
776
782
  });
777
783
  }
778
- closeAgentBackendSessionExecution(execution);
779
- }
780
- function closeAgentBackendSessionExecution(execution) {
781
- if (execution.closed) return;
782
- execution.closed = true;
783
784
  }
784
785
  //#endregion
785
- //#region src/agent/agent-backend/agent-run-executor.ts
786
+ //#region src/agent/agent-backend/agent-run-executor.service.ts
786
787
  var AgentRunExecutor = class {
787
- async *executeRun(session, envelope, controller) {
788
+ executeRun = (session, envelope, controller) => (async function* (self) {
788
789
  if (!isHiddenNcpMessage(envelope.message)) {
789
790
  const messageSent = {
790
791
  type: NcpEventType.MessageSent,
791
792
  payload: {
792
793
  sessionId: envelope.sessionId,
793
794
  message: structuredClone(envelope.message),
795
+ ...envelope.correlationId ? { correlationId: envelope.correlationId } : {},
794
796
  metadata: envelope.metadata
795
797
  }
796
798
  };
@@ -803,27 +805,39 @@ var AgentRunExecutor = class {
803
805
  messages: [envelope.message],
804
806
  correlationId: envelope.correlationId,
805
807
  metadata: envelope.metadata
806
- }, { signal: controller.signal })) yield structuredClone(event);
808
+ }, { signal: controller.signal })) yield normalizeRunEvent(event, envelope);
807
809
  } catch (error) {
808
810
  if (!controller.signal.aborted) {
809
- const runErrorEvent = await this.publishFailure(error, envelope, session);
811
+ const runErrorEvent = await self.publishFailure(error, envelope, session);
810
812
  yield structuredClone(runErrorEvent);
811
813
  }
812
814
  }
813
- }
814
- async publishFailure(error, envelope, session) {
815
+ })(this);
816
+ publishFailure = async (error, envelope, session) => {
815
817
  const message = error instanceof Error ? error.message : String(error);
816
818
  const runErrorEvent = {
817
819
  type: NcpEventType.RunError,
818
820
  payload: {
819
821
  sessionId: envelope.sessionId,
822
+ ...envelope.correlationId ? { correlationId: envelope.correlationId } : {},
820
823
  error: message
821
824
  }
822
825
  };
823
826
  await session.stateManager.dispatch(runErrorEvent);
824
827
  return runErrorEvent;
825
- }
828
+ };
826
829
  };
830
+ function normalizeRunEvent(event, envelope) {
831
+ if (!("payload" in event) || !event.payload || typeof event.payload !== "object") return structuredClone(event);
832
+ return structuredClone({
833
+ ...event,
834
+ payload: {
835
+ ...event.payload,
836
+ sessionId: "sessionId" in event.payload && typeof event.payload.sessionId === "string" ? event.payload.sessionId : envelope.sessionId,
837
+ ...envelope.correlationId && (!("correlationId" in event.payload) || typeof event.payload.correlationId !== "string") ? { correlationId: envelope.correlationId } : {}
838
+ }
839
+ });
840
+ }
827
841
  //#endregion
828
842
  //#region src/agent/agent-backend/async-queue.ts
829
843
  function createAsyncQueue() {
@@ -874,7 +888,7 @@ function createAsyncQueue() {
874
888
  };
875
889
  }
876
890
  //#endregion
877
- //#region src/agent/agent-backend/agent-backend-session-realtime.ts
891
+ //#region src/agent/agent-backend/agent-backend-session-realtime.service.ts
878
892
  function readEventSessionId(event) {
879
893
  const payload = "payload" in event ? event.payload : null;
880
894
  if (!payload || typeof payload !== "object") return null;
@@ -888,7 +902,7 @@ var AgentBackendSessionRealtime = class {
888
902
  if (options.dispatchToStateManager) await session.stateManager.dispatch(event);
889
903
  this.params.publishEndpointEvent(event);
890
904
  session.publisher.publish(event);
891
- if (options.persistSession !== false) await this.params.persistSession(session.sessionId);
905
+ if (options.persistSession !== false) await this.params.persistSessionEvent(session, event);
892
906
  };
893
907
  streamSessionEvents = (payloadOrParams, opts) => {
894
908
  return (async function* (self) {
@@ -958,7 +972,7 @@ var AgentBackendSessionRealtime = class {
958
972
  };
959
973
  };
960
974
  //#endregion
961
- //#region src/agent/agent-backend/agent-backend-session-utils.ts
975
+ //#region src/agent/agent-backend/agent-backend-session.utils.ts
962
976
  const AUTO_SESSION_LABEL_MAX_LENGTH = 64;
963
977
  function readOptionalAgentId$1(value) {
964
978
  if (typeof value !== "string") return;
@@ -970,6 +984,9 @@ function readMessages(snapshot) {
970
984
  if (snapshot.streamingMessage) messages.push(structuredClone(snapshot.streamingMessage));
971
985
  return messages;
972
986
  }
987
+ function readSessionActivityAt(summary) {
988
+ return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
989
+ }
973
990
  function toSessionSummary(session, liveSession) {
974
991
  const metadata = withAutoSessionLabel({
975
992
  metadata: session.metadata ? structuredClone({
@@ -982,6 +999,7 @@ function toSessionSummary(session, liveSession) {
982
999
  sessionId: session.sessionId,
983
1000
  ...readOptionalAgentId$1(session.agentId) ? { agentId: readOptionalAgentId$1(session.agentId) } : {},
984
1001
  messageCount: session.messages.length,
1002
+ ...session.createdAt ? { createdAt: session.createdAt } : {},
985
1003
  updatedAt: session.updatedAt,
986
1004
  ...session.messages.length > 0 ? { lastMessageAt: session.messages[session.messages.length - 1]?.timestamp ?? session.updatedAt } : {},
987
1005
  status: liveSession?.activeExecution ? "running" : "idle",
@@ -999,6 +1017,7 @@ function toLiveSessionSummary(session) {
999
1017
  sessionId: session.sessionId,
1000
1018
  ...readOptionalAgentId$1(session.agentId) ? { agentId: readOptionalAgentId$1(session.agentId) } : {},
1001
1019
  messageCount: messages.length,
1020
+ createdAt: session.createdAt,
1002
1021
  updatedAt,
1003
1022
  ...messages.length > 0 ? { lastMessageAt: messages[messages.length - 1]?.timestamp ?? updatedAt } : {},
1004
1023
  status: session.activeExecution ? "running" : "idle",
@@ -1020,6 +1039,36 @@ function toLiveSessionSummaryWithContextWindow(session, resolver) {
1020
1039
  const messages = readMessages(session.stateManager.getSnapshot());
1021
1040
  return withSessionContextWindow(toLiveSessionSummary(session), messages, resolver);
1022
1041
  }
1042
+ async function listBackendSessionSummaries(params) {
1043
+ const { liveSessions, sessionStore } = params;
1044
+ const summaries = sessionStore.listSessionSummaries ? await sessionStore.listSessionSummaries() : (await sessionStore.listSessions()).map((session) => toSessionSummary(session, liveSessions.find((liveSession) => liveSession.sessionId === session.sessionId) ?? null));
1045
+ for (const liveSession of liveSessions) {
1046
+ const existingIndex = summaries.findIndex((session) => session.sessionId === liveSession.sessionId);
1047
+ if (existingIndex >= 0) {
1048
+ summaries[existingIndex] = {
1049
+ ...summaries[existingIndex],
1050
+ status: liveSession.activeExecution ? "running" : "idle"
1051
+ };
1052
+ continue;
1053
+ }
1054
+ summaries.push(toLiveSessionSummary(liveSession));
1055
+ }
1056
+ return summaries.sort((left, right) => readSessionActivityAt(right).localeCompare(readSessionActivityAt(left)));
1057
+ }
1058
+ async function listBackendSessionMessages(params) {
1059
+ const { liveSession, sessionId, sessionStore } = params;
1060
+ if (liveSession) return readMessages(liveSession.stateManager.getSnapshot());
1061
+ if (sessionStore.listSessionMessages) return sessionStore.listSessionMessages(sessionId);
1062
+ const session = await sessionStore.getSession(sessionId);
1063
+ return session ? session.messages.map((message) => structuredClone(message)) : [];
1064
+ }
1065
+ async function getBackendSessionSummary(params) {
1066
+ const { liveSession, resolveSessionContextWindow, sessionId, sessionStore } = params;
1067
+ if (!liveSession && sessionStore.getSessionSummary) return sessionStore.getSessionSummary(sessionId);
1068
+ const storedSession = await sessionStore.getSession(sessionId);
1069
+ const liveMessages = liveSession ? readMessages(liveSession.stateManager.getSnapshot()) : null;
1070
+ return storedSession ? withSessionContextWindow(toSessionSummary(storedSession, liveSession), liveMessages ?? storedSession.messages, resolveSessionContextWindow) : liveSession ? toLiveSessionSummaryWithContextWindow(liveSession, resolveSessionContextWindow) : null;
1071
+ }
1023
1072
  function now() {
1024
1073
  return (/* @__PURE__ */ new Date()).toISOString();
1025
1074
  }
@@ -1101,7 +1150,7 @@ function normalizeSendRunEvent(params) {
1101
1150
  };
1102
1151
  }
1103
1152
  //#endregion
1104
- //#region src/agent/agent-backend/agent-backend-session-persistence.ts
1153
+ //#region src/agent/agent-backend/agent-backend-session-persistence.utils.ts
1105
1154
  function readOptionalAgentId(value) {
1106
1155
  if (typeof value !== "string") return;
1107
1156
  const trimmed = value.trim().toLowerCase();
@@ -1111,44 +1160,94 @@ function readAgentIdFromMetadata(metadata) {
1111
1160
  return readOptionalAgentId(metadata?.agent_id) ?? readOptionalAgentId(metadata?.agentId);
1112
1161
  }
1113
1162
  function resolvePersistedAgentId(params) {
1114
- return readOptionalAgentId(params.liveSession?.agentId) ?? readOptionalAgentId(params.storedSession?.agentId) ?? readAgentIdFromMetadata(params.liveSession?.metadata) ?? readAgentIdFromMetadata(params.storedSession?.metadata);
1163
+ const { liveSession, storedSession } = params;
1164
+ return readOptionalAgentId(liveSession?.agentId) ?? readOptionalAgentId(storedSession?.agentId) ?? readAgentIdFromMetadata(liveSession?.metadata) ?? readAgentIdFromMetadata(storedSession?.metadata);
1115
1165
  }
1116
1166
  function buildUpdatedSessionRecord(params) {
1117
- const nextMetadata = params.patch.metadata === null ? {} : params.patch.metadata ? structuredClone(params.patch.metadata) : structuredClone(params.liveSession?.metadata ?? params.storedSession?.metadata ?? {});
1118
- if (params.liveSession) params.liveSession.metadata = structuredClone(nextMetadata);
1167
+ const { liveSession, patch, sessionId, storedSession, updatedAt } = params;
1168
+ const nextMetadata = patch.metadata === null ? {} : patch.metadata ? structuredClone(patch.metadata) : structuredClone(liveSession?.metadata ?? storedSession?.metadata ?? {});
1169
+ if (liveSession) liveSession.metadata = structuredClone(nextMetadata);
1170
+ const agentId = resolvePersistedAgentId({
1171
+ liveSession,
1172
+ storedSession
1173
+ });
1119
1174
  return {
1120
- sessionId: params.sessionId,
1121
- ...resolvePersistedAgentId({
1122
- liveSession: params.liveSession,
1123
- storedSession: params.storedSession
1124
- }) ? { agentId: resolvePersistedAgentId({
1125
- liveSession: params.liveSession,
1126
- storedSession: params.storedSession
1127
- }) } : {},
1128
- messages: params.liveSession ? readMessages(params.liveSession.stateManager.getSnapshot()) : params.storedSession?.messages.map((message) => structuredClone(message)) ?? [],
1129
- updatedAt: params.updatedAt,
1175
+ sessionId,
1176
+ ...agentId ? { agentId } : {},
1177
+ messages: liveSession ? readMessages(liveSession.stateManager.getSnapshot()) : storedSession?.messages.map((message) => structuredClone(message)) ?? [],
1178
+ createdAt: storedSession?.createdAt ?? liveSession?.createdAt ?? updatedAt,
1179
+ updatedAt,
1130
1180
  metadata: nextMetadata
1131
1181
  };
1132
1182
  }
1133
1183
  function buildPersistedLiveSessionRecord(params) {
1134
- const messages = readMessages(params.session.stateManager.getSnapshot());
1184
+ const { session, sessionId, updatedAt } = params;
1185
+ const messages = readMessages(session.stateManager.getSnapshot());
1135
1186
  const metadata = withAutoSessionLabel({
1136
1187
  metadata: {
1137
- ...params.session.metadata ? structuredClone(params.session.metadata) : {},
1138
- ...params.session.activeExecution?.requestEnvelope.metadata ? structuredClone(params.session.activeExecution.requestEnvelope.metadata) : {}
1188
+ ...session.metadata ? structuredClone(session.metadata) : {},
1189
+ ...session.activeExecution?.requestEnvelope.metadata ? structuredClone(session.activeExecution.requestEnvelope.metadata) : {}
1139
1190
  },
1140
1191
  messages
1141
1192
  });
1193
+ const agentId = readOptionalAgentId(session.agentId) ?? readAgentIdFromMetadata(session.metadata) ?? readAgentIdFromMetadata(session.activeExecution?.requestEnvelope.metadata);
1142
1194
  return {
1143
- sessionId: params.sessionId,
1144
- ...readOptionalAgentId(params.session.agentId) ?? readAgentIdFromMetadata(params.session.metadata) ?? readAgentIdFromMetadata(params.session.activeExecution?.requestEnvelope.metadata) ? { agentId: readOptionalAgentId(params.session.agentId) ?? readAgentIdFromMetadata(params.session.metadata) ?? readAgentIdFromMetadata(params.session.activeExecution?.requestEnvelope.metadata) } : {},
1195
+ sessionId,
1196
+ ...agentId ? { agentId } : {},
1145
1197
  messages,
1146
- updatedAt: params.updatedAt,
1198
+ createdAt: session.createdAt,
1199
+ updatedAt,
1200
+ metadata
1201
+ };
1202
+ }
1203
+ function buildLiveSessionEventRecord(params) {
1204
+ const { session, updatedAt } = params;
1205
+ const metadata = {
1206
+ ...session.metadata ? structuredClone(session.metadata) : {},
1207
+ ...session.activeExecution?.requestEnvelope.metadata ? structuredClone(session.activeExecution.requestEnvelope.metadata) : {}
1208
+ };
1209
+ const agentId = readOptionalAgentId(session.agentId) ?? readAgentIdFromMetadata(session.metadata) ?? readAgentIdFromMetadata(session.activeExecution?.requestEnvelope.metadata);
1210
+ return {
1211
+ sessionId: session.sessionId,
1212
+ ...agentId ? { agentId } : {},
1213
+ createdAt: session.createdAt,
1214
+ updatedAt,
1147
1215
  metadata
1148
1216
  };
1149
1217
  }
1218
+ function shouldPersistRunEndSnapshot(sessionStore) {
1219
+ return !sessionStore.appendSessionEvent;
1220
+ }
1221
+ async function persistLiveSession(params) {
1222
+ const { session, sessionId, sessionStore, updatedAt } = params;
1223
+ if (!session) return;
1224
+ await sessionStore.saveSession(buildPersistedLiveSessionRecord({
1225
+ sessionId,
1226
+ session,
1227
+ updatedAt
1228
+ }));
1229
+ }
1230
+ async function persistLiveSessionEvent(params) {
1231
+ const { event, session, sessionStore, updatedAt } = params;
1232
+ if (sessionStore.appendSessionEvent) {
1233
+ await sessionStore.appendSessionEvent({
1234
+ session: buildLiveSessionEventRecord({
1235
+ session,
1236
+ updatedAt
1237
+ }),
1238
+ event,
1239
+ updatedAt
1240
+ });
1241
+ return;
1242
+ }
1243
+ await sessionStore.saveSession(buildPersistedLiveSessionRecord({
1244
+ sessionId: session.sessionId,
1245
+ session,
1246
+ updatedAt
1247
+ }));
1248
+ }
1150
1249
  //#endregion
1151
- //#region src/agent/agent-backend/agent-backend.ts
1250
+ //#region src/agent/agent-backend/agent-backend.service.ts
1152
1251
  const DEFAULT_SUPPORTED_PART_TYPES = [
1153
1252
  "text",
1154
1253
  "file",
@@ -1161,6 +1260,9 @@ const DEFAULT_SUPPORTED_PART_TYPES = [
1161
1260
  "action",
1162
1261
  "extension"
1163
1262
  ];
1263
+ const disposeRuntime = async (runtime) => {
1264
+ await runtime.dispose?.();
1265
+ };
1164
1266
  var DefaultNcpAgentBackend = class {
1165
1267
  manifest;
1166
1268
  sessionStore;
@@ -1183,7 +1285,18 @@ var DefaultNcpAgentBackend = class {
1183
1285
  sessionStore: this.sessionStore,
1184
1286
  publishEndpointEvent: (event) => this.publisher.publish(event),
1185
1287
  subscribeEndpointEvent: (listener) => this.publisher.subscribe(listener),
1186
- persistSession: (sessionId) => this.persistSession(sessionId),
1288
+ persistSession: (sessionId) => persistLiveSession({
1289
+ sessionStore: this.sessionStore,
1290
+ session: this.sessionRegistry.getSession(sessionId),
1291
+ sessionId,
1292
+ updatedAt: now()
1293
+ }),
1294
+ persistSessionEvent: (session, event) => persistLiveSessionEvent({
1295
+ sessionStore: this.sessionStore,
1296
+ session,
1297
+ event,
1298
+ updatedAt: now()
1299
+ }),
1187
1300
  getSessionSummary: (sessionId) => this.getSession(sessionId)
1188
1301
  });
1189
1302
  this.manifest = {
@@ -1211,12 +1324,14 @@ var DefaultNcpAgentBackend = class {
1211
1324
  const execution = session.activeExecution;
1212
1325
  if (!execution) {
1213
1326
  session.publisher.close();
1327
+ await disposeRuntime(session.runtime);
1214
1328
  continue;
1215
1329
  }
1216
1330
  execution.abortHandled = true;
1217
1331
  execution.controller.abort();
1218
1332
  this.finishSessionExecution(session, execution);
1219
1333
  session.publisher.close();
1334
+ await disposeRuntime(session.runtime);
1220
1335
  }
1221
1336
  this.sessionRegistry.clear();
1222
1337
  };
@@ -1224,7 +1339,15 @@ var DefaultNcpAgentBackend = class {
1224
1339
  await this.ensureStarted();
1225
1340
  switch (event.type) {
1226
1341
  case NcpEventType.MessageRequest:
1227
- for await (const emittedEvent of this.send(event.payload));
1342
+ if (!event.payload.sessionId) throw new Error("MessageRequest requires a sessionId before it reaches the agent backend.");
1343
+ for await (const emittedEvent of this.send({
1344
+ ...event.payload,
1345
+ sessionId: event.payload.sessionId,
1346
+ message: {
1347
+ ...event.payload.message,
1348
+ sessionId: event.payload.sessionId
1349
+ }
1350
+ }));
1228
1351
  return;
1229
1352
  case NcpEventType.MessageStreamRequest:
1230
1353
  await this.ensureStarted();
@@ -1266,31 +1389,38 @@ var DefaultNcpAgentBackend = class {
1266
1389
  }
1267
1390
  } finally {
1268
1391
  self.finishSessionExecution(session, execution);
1269
- await self.persistSession(session.sessionId);
1392
+ if (shouldPersistRunEndSnapshot(self.sessionStore)) await persistLiveSession({
1393
+ sessionStore: self.sessionStore,
1394
+ session,
1395
+ sessionId: session.sessionId,
1396
+ updatedAt: now()
1397
+ });
1270
1398
  }
1271
1399
  })(this);
1272
1400
  };
1273
1401
  abort = async (payload) => this.handleAbort(payload);
1274
1402
  stream = (payloadOrParams, opts) => this.sessionRealtime.streamSessionEvents(payloadOrParams, opts);
1275
1403
  listSessions = async () => {
1276
- const summaries = (await this.sessionStore.listSessions()).map((session) => toSessionSummary(session, this.sessionRegistry.getSession(session.sessionId)));
1277
- for (const liveSession of this.sessionRegistry.listSessions()) {
1278
- if (summaries.some((session) => session.sessionId === liveSession.sessionId)) continue;
1279
- summaries.push(toLiveSessionSummary(liveSession));
1280
- }
1281
- return summaries.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
1404
+ return listBackendSessionSummaries({
1405
+ sessionStore: this.sessionStore,
1406
+ liveSessions: this.sessionRegistry.listSessions()
1407
+ });
1282
1408
  };
1409
+ isLiveSessionRunning = (sessionId) => Boolean(this.sessionRegistry.getSession(sessionId)?.activeExecution);
1283
1410
  listSessionMessages = async (sessionId) => {
1284
- const liveSession = this.sessionRegistry.getSession(sessionId);
1285
- if (liveSession) return readMessages(liveSession.stateManager.getSnapshot());
1286
- const session = await this.sessionStore.getSession(sessionId);
1287
- return session ? session.messages.map((message) => structuredClone(message)) : [];
1411
+ return listBackendSessionMessages({
1412
+ sessionStore: this.sessionStore,
1413
+ liveSession: this.sessionRegistry.getSession(sessionId),
1414
+ sessionId
1415
+ });
1288
1416
  };
1289
1417
  getSession = async (sessionId) => {
1290
- const liveSession = this.sessionRegistry.getSession(sessionId);
1291
- const storedSession = await this.sessionStore.getSession(sessionId);
1292
- const liveMessages = liveSession ? readMessages(liveSession.stateManager.getSnapshot()) : null;
1293
- return storedSession ? withSessionContextWindow(toSessionSummary(storedSession, liveSession), liveMessages ?? storedSession.messages, this.resolveSessionContextWindow) : liveSession ? toLiveSessionSummaryWithContextWindow(liveSession, this.resolveSessionContextWindow) : null;
1418
+ return getBackendSessionSummary({
1419
+ sessionStore: this.sessionStore,
1420
+ liveSession: this.sessionRegistry.getSession(sessionId),
1421
+ sessionId,
1422
+ resolveSessionContextWindow: this.resolveSessionContextWindow
1423
+ });
1294
1424
  };
1295
1425
  appendMessage = async (sessionId, message) => {
1296
1426
  await this.ensureStarted();
@@ -1319,9 +1449,10 @@ var DefaultNcpAgentBackend = class {
1319
1449
  if (execution) {
1320
1450
  execution.abortHandled = true;
1321
1451
  execution.controller.abort();
1322
- closeAgentBackendSessionExecution(execution);
1452
+ execution.closed = true;
1323
1453
  }
1324
1454
  liveSession?.publisher.close();
1455
+ if (liveSession) await disposeRuntime(liveSession.runtime);
1325
1456
  await this.sessionStore.deleteSession(sessionId);
1326
1457
  };
1327
1458
  ensureStarted = async () => {
@@ -1354,18 +1485,9 @@ var DefaultNcpAgentBackend = class {
1354
1485
  await this.sessionRealtime.publishSessionEvent(session, abortEvent, { dispatchToStateManager: true });
1355
1486
  this.finishSessionExecution(session, execution);
1356
1487
  };
1357
- persistSession = async (sessionId) => {
1358
- const session = this.sessionRegistry.getSession(sessionId);
1359
- if (!session) return;
1360
- await this.sessionStore.saveSession(buildPersistedLiveSessionRecord({
1361
- sessionId,
1362
- session,
1363
- updatedAt: now()
1364
- }));
1365
- };
1366
1488
  };
1367
1489
  //#endregion
1368
- //#region src/agent/agent-backend/in-memory-agent-session-store.ts
1490
+ //#region src/agent/agent-backend/in-memory-agent-session.store.ts
1369
1491
  var InMemoryAgentSessionStore = class {
1370
1492
  sessions = /* @__PURE__ */ new Map();
1371
1493
  getSession = async (sessionId) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-toolkit",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "private": false,
5
5
  "description": "Toolkit implementations built on top of the NextClaw Communication Protocol.",
6
6
  "type": "module",
@@ -15,14 +15,14 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@nextclaw/ncp": "0.5.8"
18
+ "@nextclaw/ncp": "0.5.10"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.17.6",
22
22
  "prettier": "^3.3.3",
23
23
  "typescript": "^5.6.3",
24
24
  "vitest": "^4.1.2",
25
- "@nextclaw/ncp-agent-runtime": "0.3.18"
25
+ "@nextclaw/ncp-agent-runtime": "0.3.20"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension",