@nextclaw/kernel 0.1.3 → 0.1.5

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 +10 -0
  2. package/dist/index.js +677 -94
  3. package/package.json +14 -14
package/dist/index.d.ts CHANGED
@@ -279,6 +279,7 @@ type AgentRuntimeHandle = AgentRuntimeEndpoint & {
279
279
  };
280
280
  declare function createAgentRuntimeHandle(params: {
281
281
  backend: DefaultNcpAgentBackend;
282
+ agentClientEndpoint?: NcpAgentClientEndpoint;
282
283
  runtimeRegistry: AgentRuntimeRegistry;
283
284
  refreshPluginRuntimeRegistrations: () => void;
284
285
  refreshConfiguredRuntimeEntries: () => void;
@@ -495,6 +496,8 @@ declare class AgentRuntimeManager {
495
496
  get currentHandle(): AgentRuntimeHandle | null;
496
497
  connectGatewayController: (gatewayController: GatewayController) => void;
497
498
  bootstrap: () => Promise<AgentRuntimeHandle>;
499
+ private readonly materializeAgentSendEnvelope;
500
+ private readonly createMaterializingAgentClientEndpoint;
498
501
  warmDerivedCapabilities: () => Promise<void>;
499
502
  dispose: () => Promise<void>;
500
503
  private readonly registerCoreRuntimes;
@@ -653,9 +656,14 @@ declare class ToolManager {
653
656
  }
654
657
  //#endregion
655
658
  //#region src/services/ncp-session-api.service.d.ts
659
+ type NcpAgentSessionReadableStore = AgentSessionStore & {
660
+ listSessionSummaries?: () => Promise<NcpSessionSummary[]>;
661
+ listSessionMessages?: (sessionId: string) => Promise<NcpMessage[]>;
662
+ };
656
663
  type NcpSessionApiServiceOptions = {
657
664
  eventBus: EventBus;
658
665
  getConfig: () => Config;
666
+ ncpAgentSessionStore?: NcpAgentSessionReadableStore;
659
667
  sessionManager: SessionManager;
660
668
  };
661
669
  declare class NcpSessionApiService implements NcpSessionApi {
@@ -713,6 +721,7 @@ declare class NextclawKernel {
713
721
  readonly learningLoop: LearningLoopManager;
714
722
  private readonly sessionLifecycleEvents;
715
723
  private readonly ncpAgentSessionStore;
724
+ private readonly ncpAgentSessionJournalStore;
716
725
  private startPromise;
717
726
  constructor(options?: NextclawKernelOptions);
718
727
  start: () => Promise<void>;
@@ -952,6 +961,7 @@ declare function createNcpSessionSummary(params: {
952
961
  sessionId: string;
953
962
  agentId?: string;
954
963
  messages: readonly NcpMessage[];
964
+ createdAt: string;
955
965
  updatedAt: string;
956
966
  status: NcpSessionStatus;
957
967
  metadata?: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ import { AgentRouteResolver, CONTEXT_COMPACTION_METADATA_KEY, ChannelManager, Ch
3
3
  import { EventBus, Ingress, createAppEventKey, createTypedKey, eventKeys } from "@nextclaw/shared";
4
4
  import { DefaultNcpAgentRuntime, LocalAssetStore, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool } from "@nextclaw/ncp-agent-runtime";
5
5
  import { NcpEventType, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
6
- import { DefaultNcpAgentBackend, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
6
+ import { DefaultNcpAgentBackend, DefaultNcpAgentConversationStateManager, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
7
7
  import { basename, delimiter, dirname, join, resolve } from "node:path";
8
- import { access, readFile } from "node:fs/promises";
8
+ import { access, appendFile, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
9
9
  import { appendFileSync, constants, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
10
  import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
11
11
  import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
@@ -592,10 +592,10 @@ var AgentRuntimeRegistry = class {
592
592
  //#endregion
593
593
  //#region src/features/ncp-dispatch/utils/agent-runtime-handle.utils.ts
594
594
  function createAgentRuntimeHandle(params) {
595
- const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, refreshConfiguredRuntimeEntries, applyMcpConfig, dispose, assetStore } = params;
595
+ const { agentClientEndpoint, backend, runtimeRegistry, refreshPluginRuntimeRegistrations, refreshConfiguredRuntimeEntries, applyMcpConfig, dispose, assetStore } = params;
596
596
  return {
597
597
  basePath: "/api/ncp/agent",
598
- agentClientEndpoint: createAgentClientFromServer(backend),
598
+ agentClientEndpoint: agentClientEndpoint ?? createAgentClientFromServer(backend),
599
599
  streamProvider: backend,
600
600
  runApi: backend,
601
601
  sessionApi: backend,
@@ -625,11 +625,11 @@ function normalizeString(value) {
625
625
  const trimmed = value.trim();
626
626
  return trimmed.length > 0 ? trimmed : null;
627
627
  }
628
- function isRecord$3(value) {
628
+ function isRecord$4(value) {
629
629
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
630
630
  }
631
631
  function cloneMetadata(value) {
632
- return isRecord$3(value) ? structuredClone(value) : void 0;
632
+ return isRecord$4(value) ? structuredClone(value) : void 0;
633
633
  }
634
634
  function readStringArray(value) {
635
635
  if (!Array.isArray(value)) return null;
@@ -1876,7 +1876,7 @@ var LlmUsageManager = class {
1876
1876
  };
1877
1877
  //#endregion
1878
1878
  //#region src/tools/session-search.tools.ts
1879
- function isRecord$2(value) {
1879
+ function isRecord$3(value) {
1880
1880
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1881
1881
  }
1882
1882
  function readOptionalInteger(value) {
@@ -1926,7 +1926,7 @@ var SessionSearchTool = class {
1926
1926
  return issues;
1927
1927
  };
1928
1928
  execute = async (args) => {
1929
- if (!isRecord$2(args)) throw new Error("session_search requires an object argument.");
1929
+ if (!isRecord$3(args)) throw new Error("session_search requires an object argument.");
1930
1930
  const issues = this.validateArgs(args);
1931
1931
  if (issues.length > 0) throw new Error(issues.join(" "));
1932
1932
  return this.queryService.search({
@@ -1939,13 +1939,13 @@ var SessionSearchTool = class {
1939
1939
  };
1940
1940
  //#endregion
1941
1941
  //#region src/features/native-runtime/tools/ncp-asset.tools.ts
1942
- function readOptionalString$2(value) {
1942
+ function readOptionalString$3(value) {
1943
1943
  if (typeof value !== "string") return null;
1944
1944
  const trimmed = value.trim();
1945
1945
  return trimmed.length > 0 ? trimmed : null;
1946
1946
  }
1947
1947
  function readOptionalBase64Bytes(value) {
1948
- const base64 = readOptionalString$2(value);
1948
+ const base64 = readOptionalString$3(value);
1949
1949
  if (!base64) return null;
1950
1950
  try {
1951
1951
  return Buffer.from(base64, "base64");
@@ -1996,18 +1996,18 @@ var AssetPutTool = class {
1996
1996
  this.contentBasePath = contentBasePath;
1997
1997
  }
1998
1998
  validateArgs = (args) => {
1999
- const path = readOptionalString$2(args.path);
2000
- const bytesBase64 = readOptionalString$2(args.bytesBase64);
2001
- const fileName = readOptionalString$2(args.fileName);
1999
+ const path = readOptionalString$3(args.path);
2000
+ const bytesBase64 = readOptionalString$3(args.bytesBase64);
2001
+ const fileName = readOptionalString$3(args.fileName);
2002
2002
  if (path && bytesBase64) return ["Provide either path, or bytesBase64 + fileName, not both."];
2003
2003
  if (path) return [];
2004
2004
  if (bytesBase64) return fileName ? [] : ["fileName is required when using bytesBase64."];
2005
2005
  return ["Provide either path, or bytesBase64 + fileName."];
2006
2006
  };
2007
2007
  execute = async (args) => {
2008
- const path = readOptionalString$2(args?.path);
2009
- const fileName = readOptionalString$2(args?.fileName);
2010
- const mimeType = readOptionalString$2(args?.mimeType);
2008
+ const path = readOptionalString$3(args?.path);
2009
+ const fileName = readOptionalString$3(args?.fileName);
2010
+ const mimeType = readOptionalString$3(args?.mimeType);
2011
2011
  const bytes = readOptionalBase64Bytes(args?.bytesBase64);
2012
2012
  if (path) return {
2013
2013
  ok: true,
@@ -2050,8 +2050,8 @@ var AssetExportTool = class {
2050
2050
  this.assetStore = assetStore;
2051
2051
  }
2052
2052
  execute = async (args) => {
2053
- const assetUri = readOptionalString$2(args?.assetUri);
2054
- const targetPath = readOptionalString$2(args?.targetPath);
2053
+ const assetUri = readOptionalString$3(args?.assetUri);
2054
+ const targetPath = readOptionalString$3(args?.targetPath);
2055
2055
  if (!assetUri || !targetPath) throw new Error("asset_export requires assetUri and targetPath.");
2056
2056
  return {
2057
2057
  ok: true,
@@ -2077,7 +2077,7 @@ var AssetStatTool = class {
2077
2077
  this.contentBasePath = contentBasePath;
2078
2078
  }
2079
2079
  execute = async (args) => {
2080
- const assetUri = readOptionalString$2(args?.assetUri);
2080
+ const assetUri = readOptionalString$3(args?.assetUri);
2081
2081
  if (!assetUri) throw new Error("asset_stat requires assetUri.");
2082
2082
  const record = await this.assetStore.statRecord(assetUri);
2083
2083
  if (!record) return {
@@ -2252,7 +2252,7 @@ function readRequiredString$1(params, key) {
2252
2252
  if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${key} must be a non-empty string.`);
2253
2253
  return value.trim();
2254
2254
  }
2255
- function readOptionalString$1(params, key) {
2255
+ function readOptionalString$2(params, key) {
2256
2256
  const value = params[key];
2257
2257
  if (typeof value !== "string") return;
2258
2258
  const trimmed = value.trim();
@@ -2313,7 +2313,7 @@ var SessionRequestTool = class extends Tool {
2313
2313
  const target = params.target;
2314
2314
  if (!target || typeof target !== "object" || Array.isArray(target)) throw new Error("target must be an object.");
2315
2315
  const task = readRequiredString$1(params, "task");
2316
- const notifyMode = readOptionalString$1(params, "notify")?.toLowerCase();
2316
+ const notifyMode = readOptionalString$2(params, "notify")?.toLowerCase();
2317
2317
  if (notifyMode !== "none" && notifyMode !== "final_reply") throw new Error("notify must be \"none\" or \"final_reply\".");
2318
2318
  const { toolCallId, updateToolCallResult } = context;
2319
2319
  return this.manager.requestSession({
@@ -2322,7 +2322,7 @@ var SessionRequestTool = class extends Tool {
2322
2322
  updateToolCallResult,
2323
2323
  targetSessionId: readRequiredString$1(target, "session_id"),
2324
2324
  task,
2325
- title: readOptionalString$1(params, "title"),
2325
+ title: readOptionalString$2(params, "title"),
2326
2326
  notify: notifyMode,
2327
2327
  handoffDepth: this.handoffDepth
2328
2328
  });
@@ -2334,13 +2334,13 @@ function readRequiredString(value, key) {
2334
2334
  if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${key} must be a non-empty string.`);
2335
2335
  return value.trim();
2336
2336
  }
2337
- function readOptionalString(value) {
2337
+ function readOptionalString$1(value) {
2338
2338
  if (typeof value !== "string") return;
2339
2339
  const trimmed = value.trim();
2340
2340
  return trimmed.length > 0 ? trimmed : void 0;
2341
2341
  }
2342
2342
  function readSpawnScope(value) {
2343
- const normalized = readOptionalString(value)?.toLowerCase();
2343
+ const normalized = readOptionalString$1(value)?.toLowerCase();
2344
2344
  if (!normalized || normalized === "standalone") return "standalone";
2345
2345
  if (normalized === "child") return "child";
2346
2346
  throw new Error("scope must be \"standalone\" or \"child\".");
@@ -2348,7 +2348,7 @@ function readSpawnScope(value) {
2348
2348
  function readSpawnRequestOptions(value) {
2349
2349
  if (typeof value === "undefined") return;
2350
2350
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("request must be an object.");
2351
- const notifyMode = readOptionalString(value.notify)?.toLowerCase();
2351
+ const notifyMode = readOptionalString$1(value.notify)?.toLowerCase();
2352
2352
  if (notifyMode === "none" || notifyMode === "final_reply") return { notify: notifyMode };
2353
2353
  throw new Error("request.notify must be \"none\" or \"final_reply\".");
2354
2354
  }
@@ -2429,10 +2429,10 @@ var SessionSpawnTool = class extends Tool {
2429
2429
  updateToolCallResult,
2430
2430
  sourceSessionMetadata: this.sourceSessionMetadata,
2431
2431
  task,
2432
- title: readOptionalString(rawTitle),
2433
- agentId: readOptionalString(rawAgentId),
2434
- model: readOptionalString(rawModel),
2435
- runtime: readOptionalString(rawRuntime),
2432
+ title: readOptionalString$1(rawTitle),
2433
+ agentId: readOptionalString$1(rawAgentId),
2434
+ model: readOptionalString$1(rawModel),
2435
+ runtime: readOptionalString$1(rawRuntime),
2436
2436
  handoffDepth: this.handoffDepth,
2437
2437
  ...parentSessionId ? { parentSessionId } : {},
2438
2438
  notify: request.notify
@@ -2440,11 +2440,11 @@ var SessionSpawnTool = class extends Tool {
2440
2440
  const session = this.sessionManager.createSession({
2441
2441
  sourceSessionId: this.sourceSessionId,
2442
2442
  task,
2443
- title: readOptionalString(rawTitle),
2443
+ title: readOptionalString$1(rawTitle),
2444
2444
  sourceSessionMetadata: this.sourceSessionMetadata,
2445
- agentId: readOptionalString(rawAgentId),
2446
- model: readOptionalString(rawModel),
2447
- runtime: readOptionalString(rawRuntime),
2445
+ agentId: readOptionalString$1(rawAgentId),
2446
+ model: readOptionalString$1(rawModel),
2447
+ runtime: readOptionalString$1(rawRuntime),
2448
2448
  ...parentSessionId ? { parentSessionId } : {}
2449
2449
  });
2450
2450
  this.onSessionUpdated?.(session.sessionId);
@@ -2469,10 +2469,10 @@ var SessionSpawnTool = class extends Tool {
2469
2469
  //#endregion
2470
2470
  //#region src/features/native-runtime/services/nextclaw-ncp-tool-registry.service.ts
2471
2471
  function toToolParams(args) {
2472
- if (isRecord$3(args)) return args;
2472
+ if (isRecord$4(args)) return args;
2473
2473
  if (typeof args === "string") try {
2474
2474
  const parsed = JSON.parse(args);
2475
- return isRecord$3(parsed) ? parsed : {};
2475
+ return isRecord$4(parsed) ? parsed : {};
2476
2476
  } catch {
2477
2477
  return {};
2478
2478
  }
@@ -2664,14 +2664,14 @@ function readAccountIdForHints(metadata, sessionMetadata) {
2664
2664
  //#endregion
2665
2665
  //#region src/features/native-runtime/services/nextclaw-ncp-context-builder.service.ts
2666
2666
  const TIME_HINT_TRIGGER_PATTERNS = [/\b(now|right now|current time|what time|today|tonight|tomorrow|yesterday|this morning|this afternoon|this evening|date)\b/i, /(现在|此刻|当前时间|现在几点|几点了|今天|今晚|今早|今晨|明天|昨天|日期)/];
2667
- function isRecord$1(value) {
2667
+ function isRecord$2(value) {
2668
2668
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2669
2669
  }
2670
2670
  function mergeInputMetadata(input) {
2671
- const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$1(message.metadata))?.metadata;
2671
+ const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$2(message.metadata))?.metadata;
2672
2672
  return {
2673
- ...isRecord$1(messageMetadata) ? structuredClone(messageMetadata) : {},
2674
- ...isRecord$1(input.metadata) ? structuredClone(input.metadata) : {}
2673
+ ...isRecord$2(messageMetadata) ? structuredClone(messageMetadata) : {},
2674
+ ...isRecord$2(input.metadata) ? structuredClone(input.metadata) : {}
2675
2675
  };
2676
2676
  }
2677
2677
  const REQUESTED_SKILLS_METADATA_READER = new RequestedSkillsMetadataReader();
@@ -2978,12 +2978,12 @@ function createContextWindowUpdatedEvent(params) {
2978
2978
  }
2979
2979
  };
2980
2980
  }
2981
- function isRecord(value) {
2981
+ function isRecord$1(value) {
2982
2982
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2983
2983
  }
2984
2984
  function resolveNativeReasoningNormalizationMode(params) {
2985
2985
  const runtimeEntry = params.config.agents.runtimes.entries.native?.config ?? params.config.ui.ncp.runtimes.native;
2986
- const runtimeMetadata = isRecord(runtimeEntry) ? runtimeEntry : {};
2986
+ const runtimeMetadata = isRecord$1(runtimeEntry) ? runtimeEntry : {};
2987
2987
  return readAssistantReasoningNormalizationModeFromMetadata(params.sessionMetadata) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalizationMode) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization_mode) ?? "think-tags";
2988
2988
  }
2989
2989
  function createSessionSearchTools(params) {
@@ -3240,6 +3240,24 @@ function createAgentRuntimeSessionRequestDispatcher(options) {
3240
3240
  }
3241
3241
  //#endregion
3242
3242
  //#region src/managers/agent-runtime.manager.ts
3243
+ function readOptionalString(value) {
3244
+ if (typeof value !== "string") return null;
3245
+ const trimmed = value.trim();
3246
+ return trimmed.length > 0 ? trimmed : null;
3247
+ }
3248
+ function readMessageTask(message) {
3249
+ for (const part of message.parts) if ((part.type === "text" || part.type === "rich-text" || part.type === "reasoning") && part.text.trim()) return part.text.trim();
3250
+ return "Session";
3251
+ }
3252
+ function readSessionMetadataString(metadata, ...keys) {
3253
+ for (const key of keys) {
3254
+ const value = readOptionalString(metadata?.[key]);
3255
+ if (value) return value;
3256
+ }
3257
+ }
3258
+ async function consumeAgentEvents(events) {
3259
+ for await (const event of events);
3260
+ }
3243
3261
  var AgentRuntimeManager = class {
3244
3262
  runtimeRegistry = new AgentRuntimeRegistry();
3245
3263
  mcpRuntimeSupport;
@@ -3321,6 +3339,7 @@ var AgentRuntimeManager = class {
3321
3339
  await this.backend.start();
3322
3340
  this.handle = createAgentRuntimeHandle({
3323
3341
  backend: this.backend,
3342
+ agentClientEndpoint: this.createMaterializingAgentClientEndpoint(this.backend),
3324
3343
  runtimeRegistry: this.runtimeRegistry,
3325
3344
  refreshPluginRuntimeRegistrations: this.pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations,
3326
3345
  refreshConfiguredRuntimeEntries: this.refreshConfiguredRuntimeEntries,
@@ -3330,6 +3349,68 @@ var AgentRuntimeManager = class {
3330
3349
  });
3331
3350
  return this.handle;
3332
3351
  };
3352
+ materializeAgentSendEnvelope = (envelope) => {
3353
+ const existingSessionId = readOptionalString(envelope.sessionId) ?? readOptionalString(envelope.message.sessionId);
3354
+ if (existingSessionId) return {
3355
+ ...envelope,
3356
+ sessionId: existingSessionId,
3357
+ message: {
3358
+ ...envelope.message,
3359
+ sessionId: existingSessionId
3360
+ }
3361
+ };
3362
+ const metadata = envelope.metadata ?? {};
3363
+ const createdSession = this.params.sessions.createSession({
3364
+ task: readMessageTask(envelope.message),
3365
+ title: readSessionMetadataString(metadata, "label", "title"),
3366
+ sourceSessionMetadata: {},
3367
+ metadataOverrides: metadata,
3368
+ agentId: readSessionMetadataString(metadata, "agent_id", "agentId"),
3369
+ model: readSessionMetadataString(metadata, "preferred_model", "model"),
3370
+ runtime: readSessionMetadataString(metadata, "runtime", "session_type"),
3371
+ sessionType: readSessionMetadataString(metadata, "session_type", "runtime"),
3372
+ thinkingLevel: readSessionMetadataString(metadata, "preferred_thinking", "thinking"),
3373
+ projectRoot: readSessionMetadataString(metadata, "project_root")
3374
+ });
3375
+ this.params.onSessionUpdated(createdSession.sessionId);
3376
+ return {
3377
+ ...envelope,
3378
+ sessionId: createdSession.sessionId,
3379
+ message: {
3380
+ ...envelope.message,
3381
+ sessionId: createdSession.sessionId
3382
+ }
3383
+ };
3384
+ };
3385
+ createMaterializingAgentClientEndpoint = (backend) => ({
3386
+ get manifest() {
3387
+ return backend.manifest;
3388
+ },
3389
+ start: backend.start,
3390
+ stop: backend.stop,
3391
+ subscribe: backend.subscribe,
3392
+ stream: async (payload) => {
3393
+ await consumeAgentEvents(backend.stream(payload));
3394
+ },
3395
+ abort: backend.abort,
3396
+ send: async (envelope) => {
3397
+ await consumeAgentEvents(backend.send(this.materializeAgentSendEnvelope(envelope)));
3398
+ },
3399
+ emit: async (event) => {
3400
+ switch (event.type) {
3401
+ case NcpEventType.MessageRequest:
3402
+ await consumeAgentEvents(backend.send(this.materializeAgentSendEnvelope(event.payload)));
3403
+ return;
3404
+ case NcpEventType.MessageStreamRequest:
3405
+ await consumeAgentEvents(backend.stream(event.payload));
3406
+ return;
3407
+ case NcpEventType.MessageAbort:
3408
+ await backend.abort(event.payload);
3409
+ return;
3410
+ default: await backend.emit(event);
3411
+ }
3412
+ }
3413
+ });
3333
3414
  warmDerivedCapabilities = async () => {
3334
3415
  this.assertNotDisposed();
3335
3416
  this.warmupPromise ??= this.runDerivedCapabilityWarmup();
@@ -4069,11 +4150,12 @@ var NcpLifecycleEventBridge = class {
4069
4150
  //#endregion
4070
4151
  //#region src/utils/ncp-session-summary.utils.ts
4071
4152
  function createNcpSessionSummary(params) {
4072
- const { sessionId, agentId, messages, updatedAt, status, metadata, contextWindow } = params;
4153
+ const { sessionId, agentId, messages, createdAt, updatedAt, status, metadata, contextWindow } = params;
4073
4154
  return {
4074
4155
  sessionId,
4075
4156
  ...agentId ? { agentId } : {},
4076
4157
  messageCount: messages.length,
4158
+ createdAt,
4077
4159
  updatedAt,
4078
4160
  ...messages.length > 0 ? { lastMessageAt: messages[messages.length - 1]?.timestamp ?? updatedAt } : {},
4079
4161
  status,
@@ -4100,6 +4182,28 @@ function buildUpdatedMetadata(params) {
4100
4182
  function normalizeSessionId(sessionId) {
4101
4183
  return sessionId.trim();
4102
4184
  }
4185
+ function toSessionCandidate(record) {
4186
+ const sessionId = normalizeSessionId(record.key);
4187
+ return sessionId ? {
4188
+ ...record,
4189
+ sessionId
4190
+ } : null;
4191
+ }
4192
+ function createSessionListSummary(record) {
4193
+ return {
4194
+ sessionId: record.sessionId,
4195
+ ...record.agentId ? { agentId: record.agentId } : {},
4196
+ messageCount: record.messageCount ?? 0,
4197
+ createdAt: record.created_at,
4198
+ updatedAt: record.updated_at,
4199
+ ...record.lastMessageAt ? { lastMessageAt: record.lastMessageAt } : {},
4200
+ status: "idle",
4201
+ metadata: structuredClone(record.metadata)
4202
+ };
4203
+ }
4204
+ function readSessionActivityAt$1(record) {
4205
+ return record.lastMessageAt ?? record.created_at;
4206
+ }
4103
4207
  var NcpSessionApiService = class {
4104
4208
  unsubscribeSessionUpdated = null;
4105
4209
  contextWindowPreview;
@@ -4133,32 +4237,36 @@ var NcpSessionApiService = class {
4133
4237
  this.options.eventBus.emit(eventKeys.sessionSummaryDelete, { sessionKey: normalizedSessionKey });
4134
4238
  };
4135
4239
  listSessions = async (options) => {
4136
- const summaries = [];
4137
- for (const record of this.options.sessionManager.listSessions()) {
4138
- const sessionId = normalizeSessionId(record.key);
4139
- if (!sessionId) continue;
4140
- const session = this.options.sessionManager.getIfExists(sessionId);
4141
- if (!session) continue;
4142
- summaries.push(createNcpSessionSummary({
4143
- sessionId,
4144
- agentId: session.agentId,
4145
- messages: toNcpMessages(sessionId, session.messages),
4146
- updatedAt: session.updatedAt.toISOString(),
4147
- status: "idle",
4148
- metadata: session.metadata
4149
- }));
4150
- }
4151
- summaries.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
4152
- return applyLimit(summaries, options?.limit);
4240
+ if (this.options.ncpAgentSessionStore?.listSessionSummaries) return applyLimit(await this.options.ncpAgentSessionStore.listSessionSummaries(), options?.limit);
4241
+ return applyLimit(this.options.sessionManager.listSessions().map(toSessionCandidate).filter((record) => Boolean(record)).sort((left, right) => readSessionActivityAt$1(right).localeCompare(readSessionActivityAt$1(left))), options?.limit).map(createSessionListSummary);
4153
4242
  };
4154
4243
  listSessionMessages = async (sessionId, options) => {
4155
4244
  const normalizedSessionId = normalizeSessionId(sessionId);
4245
+ if (normalizedSessionId && this.options.ncpAgentSessionStore?.listSessionMessages) return applyLimit(await this.options.ncpAgentSessionStore.listSessionMessages(normalizedSessionId), options?.limit);
4156
4246
  const session = normalizedSessionId ? this.options.sessionManager.getIfExists(normalizedSessionId) : null;
4157
4247
  if (!session) return [];
4158
4248
  return applyLimit(toNcpMessages(normalizedSessionId, session.messages).map((message) => structuredClone(message)), options?.limit);
4159
4249
  };
4160
4250
  getSession = async (sessionId) => {
4161
4251
  const normalizedSessionId = normalizeSessionId(sessionId);
4252
+ if (normalizedSessionId && this.options.ncpAgentSessionStore?.getSession) {
4253
+ const record = await this.options.ncpAgentSessionStore.getSession(normalizedSessionId);
4254
+ if (record) return createNcpSessionSummary({
4255
+ sessionId: normalizedSessionId,
4256
+ agentId: record.agentId,
4257
+ messages: record.messages,
4258
+ createdAt: record.createdAt ?? record.updatedAt,
4259
+ updatedAt: record.updatedAt,
4260
+ status: "idle",
4261
+ metadata: record.metadata,
4262
+ contextWindow: this.contextWindowPreview.preview({
4263
+ contextWindowOwner: "nextclaw",
4264
+ requestMetadata: record.metadata ?? {},
4265
+ sessionId: normalizedSessionId,
4266
+ sessionMessages: record.messages
4267
+ })
4268
+ });
4269
+ }
4162
4270
  const session = normalizedSessionId ? this.options.sessionManager.getIfExists(normalizedSessionId) : null;
4163
4271
  if (!session) return null;
4164
4272
  const messages = toNcpMessages(normalizedSessionId, session.messages);
@@ -4166,6 +4274,7 @@ var NcpSessionApiService = class {
4166
4274
  sessionId: normalizedSessionId,
4167
4275
  agentId: session.agentId,
4168
4276
  messages,
4277
+ createdAt: session.createdAt.toISOString(),
4169
4278
  updatedAt: session.updatedAt.toISOString(),
4170
4279
  status: "idle",
4171
4280
  metadata: session.metadata,
@@ -4179,6 +4288,21 @@ var NcpSessionApiService = class {
4179
4288
  };
4180
4289
  updateSession = async (sessionId, patch) => {
4181
4290
  const normalizedSessionId = normalizeSessionId(sessionId);
4291
+ if (normalizedSessionId && this.options.ncpAgentSessionStore) {
4292
+ const existing = await this.options.ncpAgentSessionStore.getSession(normalizedSessionId);
4293
+ if (existing) {
4294
+ await this.options.ncpAgentSessionStore.replaceSession({
4295
+ ...existing,
4296
+ metadata: buildUpdatedMetadata({
4297
+ existingMetadata: existing.metadata,
4298
+ patch
4299
+ }),
4300
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4301
+ });
4302
+ await this.publishSessionChange(normalizedSessionId);
4303
+ return await this.getSession(normalizedSessionId);
4304
+ }
4305
+ }
4182
4306
  const session = normalizedSessionId ? this.options.sessionManager.getIfExists(normalizedSessionId) : null;
4183
4307
  if (!session) return null;
4184
4308
  session.metadata = buildUpdatedMetadata({
@@ -4193,6 +4317,7 @@ var NcpSessionApiService = class {
4193
4317
  deleteSession = async (sessionId) => {
4194
4318
  const normalizedSessionId = normalizeSessionId(sessionId);
4195
4319
  if (!normalizedSessionId) return;
4320
+ if (this.options.ncpAgentSessionStore) await this.options.ncpAgentSessionStore.deleteSession(normalizedSessionId);
4196
4321
  this.options.sessionManager.delete(normalizedSessionId);
4197
4322
  await this.publishSessionChange(normalizedSessionId);
4198
4323
  };
@@ -4205,58 +4330,72 @@ function resolvePersistedSessionMetadata(params) {
4205
4330
  return mergeSessionMetadata(preserveExistingMetadata ? mergeSessionMetadata(currentMetadata, messageMetadata) : mergeSessionMetadata({}, messageMetadata), cloneMetadata(sessionRecord.metadata));
4206
4331
  }
4207
4332
  //#endregion
4208
- //#region src/services/ncp-agent-session-store-adapter.service.ts
4209
- function readAgentIdFromMetadata(metadata) {
4210
- return normalizeString(metadata?.agent_id)?.toLowerCase() ?? normalizeString(metadata?.agentId)?.toLowerCase() ?? void 0;
4211
- }
4333
+ //#region src/stores/ncp-agent-legacy-session.store.ts
4212
4334
  function resolveSessionRecordAgentId(record) {
4213
- return normalizeString(record.agentId)?.toLowerCase() ?? readAgentIdFromMetadata(record.metadata);
4335
+ return normalizeString(record.agentId)?.toLowerCase() ?? normalizeString(record.metadata?.agent_id)?.toLowerCase() ?? normalizeString(record.metadata?.agentId)?.toLowerCase();
4214
4336
  }
4215
- var NcpAgentSessionStoreAdapter = class {
4337
+ var NcpAgentLegacySessionStore = class {
4216
4338
  constructor(sessionManager, options = {}) {
4217
4339
  this.sessionManager = sessionManager;
4218
4340
  this.options = options;
4219
4341
  }
4220
- getSession = async (sessionId) => {
4342
+ getSession = (sessionId) => {
4221
4343
  const session = this.sessionManager.getIfExists(sessionId);
4222
4344
  if (!session) return null;
4223
4345
  return {
4224
4346
  sessionId,
4225
4347
  ...session.agentId ? { agentId: session.agentId } : {},
4226
4348
  messages: toNcpMessages(sessionId, session.messages),
4349
+ createdAt: session.createdAt.toISOString(),
4227
4350
  updatedAt: session.updatedAt.toISOString(),
4228
4351
  metadata: structuredClone(session.metadata)
4229
4352
  };
4230
4353
  };
4231
- listSessions = async () => {
4232
- const records = this.sessionManager.listSessions();
4233
- const sessions = [];
4234
- for (const record of records) {
4235
- const sessionId = normalizeString(record.key);
4236
- if (!sessionId) continue;
4237
- const session = this.sessionManager.getIfExists(sessionId);
4238
- if (!session) continue;
4239
- sessions.push({
4240
- sessionId,
4241
- ...session.agentId ? { agentId: session.agentId } : {},
4242
- messages: toNcpMessages(sessionId, session.messages),
4243
- updatedAt: session.updatedAt.toISOString(),
4244
- metadata: structuredClone(session.metadata)
4245
- });
4246
- }
4247
- sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
4248
- return sessions;
4354
+ getSessionSummary = (sessionId) => {
4355
+ const session = this.getSession(sessionId);
4356
+ if (!session) return null;
4357
+ const lastMessageAt = session.messages.at(-1)?.timestamp;
4358
+ return {
4359
+ sessionId: session.sessionId,
4360
+ ...session.agentId ? { agentId: session.agentId } : {},
4361
+ messageCount: session.messages.length,
4362
+ createdAt: session.createdAt,
4363
+ updatedAt: session.updatedAt,
4364
+ ...lastMessageAt ? { lastMessageAt } : {},
4365
+ status: "idle",
4366
+ metadata: session.metadata
4367
+ };
4249
4368
  };
4250
- persistSession = async (sessionRecord, options) => {
4251
- if (this.options.writeMode === "runtime-owned") return;
4369
+ listSessionMessages = (sessionId) => this.getSession(sessionId)?.messages ?? [];
4370
+ listSessionSummaries = () => this.sessionManager.listSessions().map((record) => ({
4371
+ sessionId: record.key,
4372
+ ...record.agentId ? { agentId: record.agentId } : {},
4373
+ messageCount: record.messageCount ?? 0,
4374
+ createdAt: record.created_at,
4375
+ updatedAt: record.updated_at,
4376
+ ...record.lastMessageAt ? { lastMessageAt: record.lastMessageAt } : {},
4377
+ status: "idle",
4378
+ metadata: structuredClone(record.metadata)
4379
+ }));
4380
+ saveSession = (sessionRecord) => this.persistSession(sessionRecord, true);
4381
+ replaceSession = (sessionRecord) => this.persistSession(sessionRecord, false);
4382
+ deleteSession = (sessionId) => {
4383
+ const existing = this.getSession(sessionId);
4384
+ if (!existing) return null;
4385
+ this.sessionManager.delete(sessionId);
4386
+ this.options.onSessionUpdated?.(sessionId);
4387
+ return existing;
4388
+ };
4389
+ persistSession = async (sessionRecord, preserveExistingMetadata) => {
4252
4390
  const session = this.sessionManager.getIfExists(sessionRecord.sessionId) ?? this.sessionManager.getOrCreate(sessionRecord.sessionId);
4253
4391
  const legacyMessages = toLegacyMessages(sessionRecord.messages);
4254
4392
  const nextAgentId = resolveSessionRecordAgentId(sessionRecord);
4255
4393
  if (nextAgentId) session.agentId = nextAgentId;
4394
+ if (sessionRecord.createdAt) session.createdAt = new Date(ensureIsoTimestamp(sessionRecord.createdAt, session.createdAt.toISOString()));
4256
4395
  session.metadata = resolvePersistedSessionMetadata({
4257
4396
  currentMetadata: session.metadata,
4258
4397
  sessionRecord,
4259
- preserveExistingMetadata: options.preserveExistingMetadata
4398
+ preserveExistingMetadata
4260
4399
  });
4261
4400
  this.sessionManager.clear(session);
4262
4401
  for (const message of legacyMessages) this.sessionManager.appendEvent(session, {
@@ -4268,19 +4407,457 @@ var NcpAgentSessionStoreAdapter = class {
4268
4407
  this.sessionManager.save(session);
4269
4408
  this.options.onSessionUpdated?.(sessionRecord.sessionId);
4270
4409
  };
4410
+ };
4411
+ //#endregion
4412
+ //#region src/services/ncp-agent-session-store-adapter.service.ts
4413
+ function readSessionActivityAt(record) {
4414
+ return record.messages.at(-1)?.timestamp ?? record.createdAt ?? record.updatedAt;
4415
+ }
4416
+ var NcpAgentSessionStoreAdapter = class {
4417
+ appendSessionEvent;
4418
+ legacyStore;
4419
+ constructor(sessionManager, options = {}) {
4420
+ this.options = options;
4421
+ this.legacyStore = new NcpAgentLegacySessionStore(sessionManager, { onSessionUpdated: options.onSessionUpdated });
4422
+ const journalStore = options.journalStore;
4423
+ if (journalStore) this.appendSessionEvent = async (params) => {
4424
+ const { event, session } = params;
4425
+ if (!await journalStore.hasSession(session.sessionId)) {
4426
+ const legacySession = this.legacyStore.getSession(session.sessionId);
4427
+ if (legacySession) await journalStore.replaceSession(legacySession);
4428
+ }
4429
+ await journalStore.appendSessionEvent(params);
4430
+ if (isSessionSummaryRefreshEvent(event)) this.options.onSessionUpdated?.(session.sessionId);
4431
+ };
4432
+ }
4433
+ getSession = async (sessionId) => await this.options.journalStore?.getSession(sessionId) ?? this.legacyStore.getSession(sessionId);
4434
+ getSessionSummary = async (sessionId) => await this.options.journalStore?.getSessionSummary(sessionId) ?? this.legacyStore.getSessionSummary(sessionId);
4435
+ listSessionMessages = async (sessionId) => {
4436
+ const journalStore = this.options.journalStore;
4437
+ return journalStore && await journalStore.hasSession(sessionId) ? await journalStore.listSessionMessages(sessionId) : this.legacyStore.listSessionMessages(sessionId);
4438
+ };
4439
+ listSessionSummaries = async () => {
4440
+ const journalSummaries = await this.options.journalStore?.listSessionSummaries() ?? [];
4441
+ const journalIds = new Set(journalSummaries.map((summary) => summary.sessionId));
4442
+ const legacySummaries = this.legacyStore.listSessionSummaries().filter((summary) => !journalIds.has(summary.sessionId));
4443
+ return [...journalSummaries, ...legacySummaries].sort((left, right) => readSummaryActivityAt(right).localeCompare(readSummaryActivityAt(left)));
4444
+ };
4445
+ listSessions = async () => {
4446
+ const summaries = await this.listSessionSummaries();
4447
+ const sessions = [];
4448
+ for (const summary of summaries) {
4449
+ const sessionId = summary.sessionId.trim();
4450
+ if (!sessionId) continue;
4451
+ const session = await this.getSession(sessionId);
4452
+ if (!session) continue;
4453
+ sessions.push(session);
4454
+ }
4455
+ sessions.sort((left, right) => readSessionActivityAt(right).localeCompare(readSessionActivityAt(left)));
4456
+ return sessions;
4457
+ };
4271
4458
  saveSession = async (sessionRecord) => {
4272
- await this.persistSession(sessionRecord, { preserveExistingMetadata: true });
4459
+ const journalStore = this.options.journalStore;
4460
+ if (journalStore && await journalStore.hasSession(sessionRecord.sessionId)) {
4461
+ await journalStore.replaceSession(sessionRecord);
4462
+ this.options.onSessionUpdated?.(sessionRecord.sessionId);
4463
+ return;
4464
+ }
4465
+ await this.legacyStore.saveSession(sessionRecord);
4273
4466
  };
4274
4467
  replaceSession = async (sessionRecord) => {
4275
- await this.persistSession(sessionRecord, { preserveExistingMetadata: false });
4468
+ const journalStore = this.options.journalStore;
4469
+ if (journalStore && await journalStore.hasSession(sessionRecord.sessionId)) {
4470
+ await journalStore.replaceSession(sessionRecord);
4471
+ this.options.onSessionUpdated?.(sessionRecord.sessionId);
4472
+ return;
4473
+ }
4474
+ await this.legacyStore.replaceSession(sessionRecord);
4276
4475
  };
4277
4476
  deleteSession = async (sessionId) => {
4278
- const existing = await this.getSession(sessionId);
4279
- if (!existing) return null;
4280
- this.sessionManager.delete(sessionId);
4281
- this.options.onSessionUpdated?.(sessionId);
4477
+ const journalSession = await this.options.journalStore?.deleteSession(sessionId);
4478
+ const legacySession = this.legacyStore.deleteSession(sessionId);
4479
+ if (journalSession && !legacySession) this.options.onSessionUpdated?.(sessionId);
4480
+ return journalSession ?? legacySession;
4481
+ };
4482
+ };
4483
+ function readSummaryActivityAt(summary) {
4484
+ return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
4485
+ }
4486
+ function isSessionSummaryRefreshEvent(event) {
4487
+ switch (event.type) {
4488
+ case NcpEventType.MessageSent:
4489
+ case NcpEventType.MessageCompleted:
4490
+ case NcpEventType.MessageAbort:
4491
+ case NcpEventType.RunFinished:
4492
+ case NcpEventType.RunError: return true;
4493
+ default: return false;
4494
+ }
4495
+ }
4496
+ const NCP_AGENT_SESSION_JOURNAL_INDEX_FILE = ".ncp-agent-session-index.json";
4497
+ const AUTO_SESSION_LABEL_MAX_LENGTH = 64;
4498
+ function normalizeNcpSessionId(sessionId) {
4499
+ return sessionId.trim();
4500
+ }
4501
+ function safeNcpSessionFilename(value) {
4502
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
4503
+ }
4504
+ function normalizeNcpAgentId(agentId) {
4505
+ const normalized = agentId?.trim().toLowerCase();
4506
+ return normalized ? normalized : void 0;
4507
+ }
4508
+ function isRecord(value) {
4509
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4510
+ }
4511
+ function toIsoString(value, fallback) {
4512
+ if (typeof value !== "string") return fallback;
4513
+ const parsed = Date.parse(value);
4514
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : fallback;
4515
+ }
4516
+ function createNcpAgentSessionSummary(record) {
4517
+ const metadata = structuredClone(record.metadata ?? {});
4518
+ const label = readOptionalText(metadata.label) ?? resolveAutoSessionLabel(record.messages);
4519
+ if (label) metadata.label = label;
4520
+ const lastMessageAt = readMessageTimestamp(record.messages.at(-1));
4521
+ return {
4522
+ sessionId: record.sessionId,
4523
+ ...normalizeNcpAgentId(record.agentId) ? { agentId: normalizeNcpAgentId(record.agentId) } : {},
4524
+ messageCount: record.messages.length,
4525
+ ...record.createdAt ? { createdAt: record.createdAt } : {},
4526
+ updatedAt: record.updatedAt,
4527
+ ...lastMessageAt ? { lastMessageAt } : {},
4528
+ status: "idle",
4529
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
4530
+ };
4531
+ }
4532
+ function createNcpAgentSessionJournalMetadataEntry(record) {
4533
+ return {
4534
+ _type: "metadata",
4535
+ version: 1,
4536
+ created_at: record.createdAt ?? record.updatedAt,
4537
+ updated_at: record.updatedAt,
4538
+ ...normalizeNcpAgentId(record.agentId) ? { agent_id: normalizeNcpAgentId(record.agentId) } : {},
4539
+ metadata: structuredClone(record.metadata ?? {})
4540
+ };
4541
+ }
4542
+ function upsertNcpAgentSessionSummaryEvent(params) {
4543
+ const { current, event, session, updatedAt } = params;
4544
+ const metadata = {
4545
+ ...current?.metadata ? structuredClone(current.metadata) : {},
4546
+ ...session.metadata ? structuredClone(session.metadata) : {}
4547
+ };
4548
+ const label = readOptionalText(metadata.label) ?? readSummaryLabelFromEvent(event);
4549
+ if (label) metadata.label = truncateLabel(label);
4550
+ const eventMessage = readMessageFromSummaryEvent(event);
4551
+ const messageCount = current ? current.messageCount + (eventMessage ? 1 : 0) : eventMessage ? 1 : 0;
4552
+ const lastMessageAt = readMessageTimestamp(eventMessage) ?? current?.lastMessageAt;
4553
+ return {
4554
+ sessionId: session.sessionId,
4555
+ ...normalizeNcpAgentId(session.agentId ?? current?.agentId) ? { agentId: normalizeNcpAgentId(session.agentId ?? current?.agentId) } : {},
4556
+ messageCount,
4557
+ createdAt: current?.createdAt ?? session.createdAt ?? updatedAt,
4558
+ updatedAt,
4559
+ ...lastMessageAt ? { lastMessageAt } : {},
4560
+ status: "idle",
4561
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
4562
+ };
4563
+ }
4564
+ async function replayNcpAgentSessionEvents(events) {
4565
+ const stateManager = new DefaultNcpAgentConversationStateManager();
4566
+ for (const event of events) await stateManager.dispatch(event.type === NcpEventType.MessageCompleted ? createCompletedMessageEvent(event) : structuredClone(event));
4567
+ const snapshot = stateManager.getSnapshot();
4568
+ return [...snapshot.messages.map((message) => structuredClone(message)), ...snapshot.streamingMessage ? [structuredClone(snapshot.streamingMessage)] : []];
4569
+ }
4570
+ function readNcpSessionSummaryActivityAt(summary) {
4571
+ return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
4572
+ }
4573
+ function readMessageTimestamp(message) {
4574
+ return typeof message?.timestamp === "string" && Number.isFinite(Date.parse(message.timestamp)) ? new Date(message.timestamp).toISOString() : void 0;
4575
+ }
4576
+ function truncateLabel(value) {
4577
+ const chars = Array.from(value);
4578
+ return chars.length <= AUTO_SESSION_LABEL_MAX_LENGTH ? value : `${chars.slice(0, AUTO_SESSION_LABEL_MAX_LENGTH).join("")}...`;
4579
+ }
4580
+ function readOptionalText(value) {
4581
+ if (typeof value !== "string") return null;
4582
+ const trimmed = value.trim();
4583
+ return trimmed ? trimmed : null;
4584
+ }
4585
+ function resolveAutoSessionLabel(messages) {
4586
+ for (const message of messages) {
4587
+ if (message.role !== "user") continue;
4588
+ for (const part of message.parts) if (part.type === "text" || part.type === "rich-text") {
4589
+ const text = readOptionalText(part.text);
4590
+ if (text) return truncateLabel(text);
4591
+ }
4592
+ }
4593
+ return null;
4594
+ }
4595
+ function readMessageFromSummaryEvent(event) {
4596
+ switch (event.type) {
4597
+ case NcpEventType.MessageSent: return event.payload.message;
4598
+ case NcpEventType.MessageCompleted: return event.payload.message;
4599
+ default: return;
4600
+ }
4601
+ }
4602
+ function readSummaryLabelFromEvent(event) {
4603
+ const message = readMessageFromSummaryEvent(event);
4604
+ if (message?.role !== "user") return null;
4605
+ return resolveAutoSessionLabel([message]);
4606
+ }
4607
+ function createCompletedMessageEvent(event) {
4608
+ return {
4609
+ type: NcpEventType.MessageSent,
4610
+ payload: {
4611
+ sessionId: event.payload.sessionId,
4612
+ message: structuredClone(event.payload.message),
4613
+ ...event.payload.correlationId ? { correlationId: event.payload.correlationId } : {},
4614
+ metadata: event.payload.metadata
4615
+ }
4616
+ };
4617
+ }
4618
+ //#endregion
4619
+ //#region src/stores/ncp-agent-session-journal.store.ts
4620
+ var NcpAgentSessionJournalStore = class {
4621
+ sessions = /* @__PURE__ */ new Map();
4622
+ nextSeqBySession = /* @__PURE__ */ new Map();
4623
+ writeChains = /* @__PURE__ */ new Map();
4624
+ summaryIndex = null;
4625
+ constructor(journalDir) {
4626
+ this.journalDir = journalDir;
4627
+ }
4628
+ appendSessionEvent = async (params) => {
4629
+ const sessionId = normalizeNcpSessionId(params.session.sessionId);
4630
+ if (!sessionId) return;
4631
+ const next = (this.writeChains.get(sessionId) ?? Promise.resolve()).then(() => this.appendSessionEventNow({
4632
+ ...params,
4633
+ session: {
4634
+ ...params.session,
4635
+ sessionId
4636
+ }
4637
+ }));
4638
+ this.writeChains.set(sessionId, next.catch(() => void 0));
4639
+ await next;
4640
+ };
4641
+ getSession = async (sessionId) => {
4642
+ const normalizedSessionId = normalizeNcpSessionId(sessionId);
4643
+ if (!normalizedSessionId) return null;
4644
+ const cached = this.sessions.get(normalizedSessionId);
4645
+ if (cached) return structuredClone(cached.record);
4646
+ const loaded = await this.loadSession(normalizedSessionId);
4647
+ if (!loaded) return null;
4648
+ this.sessions.set(normalizedSessionId, loaded);
4649
+ return structuredClone(loaded.record);
4650
+ };
4651
+ getSessionSummary = async (sessionId) => {
4652
+ const normalizedSessionId = normalizeNcpSessionId(sessionId);
4653
+ if (!normalizedSessionId) return null;
4654
+ const indexed = (await this.loadSummaryIndex()).get(normalizedSessionId);
4655
+ if (indexed) return structuredClone(indexed);
4656
+ const record = await this.getSession(normalizedSessionId);
4657
+ return record ? createNcpAgentSessionSummary(record) : null;
4658
+ };
4659
+ listSessionSummaries = async () => {
4660
+ return [...(await this.loadSummaryIndex()).values()].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
4661
+ };
4662
+ listSessionMessages = async (sessionId) => {
4663
+ const session = await this.getSession(sessionId);
4664
+ return session ? session.messages.map((message) => structuredClone(message)) : [];
4665
+ };
4666
+ replaceSession = async (record) => {
4667
+ const sessionId = normalizeNcpSessionId(record.sessionId);
4668
+ if (!sessionId) return;
4669
+ await this.ensureJournalDir();
4670
+ const nextRecord = structuredClone({
4671
+ ...record,
4672
+ sessionId
4673
+ });
4674
+ const entries = [createNcpAgentSessionJournalMetadataEntry(nextRecord), ...nextRecord.messages.map((message, index) => ({
4675
+ _type: "event",
4676
+ version: 1,
4677
+ seq: index + 1,
4678
+ timestamp: message.timestamp ?? nextRecord.updatedAt,
4679
+ event: {
4680
+ type: NcpEventType.MessageSent,
4681
+ payload: {
4682
+ sessionId,
4683
+ message: structuredClone(message)
4684
+ }
4685
+ }
4686
+ }))];
4687
+ await writeFile(this.sessionPath(sessionId), `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
4688
+ const nextSeq = nextRecord.messages.length + 1;
4689
+ this.sessions.set(sessionId, {
4690
+ record: nextRecord,
4691
+ nextSeq
4692
+ });
4693
+ this.nextSeqBySession.set(sessionId, nextSeq);
4694
+ await this.upsertSummaryIndex(createNcpAgentSessionSummary(nextRecord));
4695
+ };
4696
+ deleteSession = async (sessionId) => {
4697
+ const normalizedSessionId = normalizeNcpSessionId(sessionId);
4698
+ if (!normalizedSessionId) return null;
4699
+ const existing = await this.getSession(normalizedSessionId);
4700
+ this.sessions.delete(normalizedSessionId);
4701
+ this.nextSeqBySession.delete(normalizedSessionId);
4702
+ try {
4703
+ await unlink(this.sessionPath(normalizedSessionId));
4704
+ } catch {}
4705
+ await this.removeSummaryIndex(normalizedSessionId);
4282
4706
  return existing;
4283
4707
  };
4708
+ hasSession = async (sessionId) => {
4709
+ const normalizedSessionId = normalizeNcpSessionId(sessionId);
4710
+ if (!normalizedSessionId) return false;
4711
+ return (await this.loadSummaryIndex()).has(normalizedSessionId);
4712
+ };
4713
+ appendSessionEventNow = async (params) => {
4714
+ const { event, session, updatedAt } = params;
4715
+ const { sessionId } = session;
4716
+ await this.ensureJournalDir();
4717
+ const existing = this.sessions.get(sessionId) ?? (this.nextSeqBySession.has(sessionId) ? null : await this.loadSession(sessionId));
4718
+ const hasJournal = Boolean(existing) || this.nextSeqBySession.has(sessionId);
4719
+ const nextSeq = this.nextSeqBySession.get(sessionId) ?? existing?.nextSeq ?? 1;
4720
+ const path = this.sessionPath(sessionId);
4721
+ if (!hasJournal) await appendFile(path, `${JSON.stringify(createNcpAgentSessionJournalMetadataEntry(session))}\n`, "utf-8");
4722
+ const entry = {
4723
+ _type: "event",
4724
+ version: 1,
4725
+ seq: nextSeq,
4726
+ timestamp: updatedAt,
4727
+ event: structuredClone(event)
4728
+ };
4729
+ await appendFile(path, `${JSON.stringify(entry)}\n`, "utf-8");
4730
+ this.nextSeqBySession.set(sessionId, nextSeq + 1);
4731
+ this.sessions.delete(sessionId);
4732
+ await this.upsertSummaryIndexForEvent({
4733
+ session,
4734
+ event,
4735
+ updatedAt
4736
+ });
4737
+ };
4738
+ loadSession = async (sessionId) => {
4739
+ let raw;
4740
+ try {
4741
+ raw = await readFile(this.sessionPath(sessionId), "utf-8");
4742
+ } catch {
4743
+ return null;
4744
+ }
4745
+ const { agentId, createdAt, events, metadata, nextSeq, updatedAt } = this.parseSessionJournal(raw);
4746
+ const messages = await replayNcpAgentSessionEvents(events);
4747
+ const record = {
4748
+ sessionId,
4749
+ ...agentId ? { agentId } : {},
4750
+ messages,
4751
+ createdAt,
4752
+ updatedAt,
4753
+ metadata
4754
+ };
4755
+ this.nextSeqBySession.set(sessionId, nextSeq);
4756
+ return {
4757
+ record,
4758
+ nextSeq
4759
+ };
4760
+ };
4761
+ parseSessionJournal = (raw) => {
4762
+ let metadata = {};
4763
+ let agentId;
4764
+ let createdAt = (/* @__PURE__ */ new Date()).toISOString();
4765
+ let updatedAt = createdAt;
4766
+ let nextSeq = 1;
4767
+ const events = [];
4768
+ for (const line of raw.split("\n")) {
4769
+ if (!line.trim()) continue;
4770
+ const parsed = JSON.parse(line);
4771
+ if (!isRecord(parsed)) continue;
4772
+ if (parsed._type === "metadata") {
4773
+ metadata = isRecord(parsed.metadata) ? structuredClone(parsed.metadata) : {};
4774
+ agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
4775
+ createdAt = toIsoString(parsed.created_at, createdAt);
4776
+ updatedAt = toIsoString(parsed.updated_at, updatedAt);
4777
+ continue;
4778
+ }
4779
+ if (parsed._type === "event" && isRecord(parsed.event)) {
4780
+ const seq = Number(parsed.seq);
4781
+ nextSeq = Math.max(nextSeq, Number.isFinite(seq) ? Math.trunc(seq) + 1 : nextSeq);
4782
+ updatedAt = toIsoString(parsed.timestamp, updatedAt);
4783
+ events.push(structuredClone(parsed.event));
4784
+ }
4785
+ }
4786
+ return {
4787
+ metadata,
4788
+ ...agentId ? { agentId } : {},
4789
+ createdAt,
4790
+ updatedAt,
4791
+ nextSeq,
4792
+ events
4793
+ };
4794
+ };
4795
+ loadSummaryIndex = async () => {
4796
+ if (this.summaryIndex) return this.summaryIndex;
4797
+ try {
4798
+ const parsed = JSON.parse(await readFile(this.indexPath(), "utf-8"));
4799
+ if (parsed.version === 1 && Array.isArray(parsed.records)) {
4800
+ this.summaryIndex = new Map(parsed.records.map((record) => [record.sessionId, structuredClone(record)]));
4801
+ return this.summaryIndex;
4802
+ }
4803
+ } catch {}
4804
+ this.summaryIndex = await this.rebuildSummaryIndex();
4805
+ await this.persistSummaryIndex();
4806
+ return this.summaryIndex;
4807
+ };
4808
+ rebuildSummaryIndex = async () => {
4809
+ const records = /* @__PURE__ */ new Map();
4810
+ let entries = [];
4811
+ try {
4812
+ entries = await readdir(this.journalDir);
4813
+ } catch {
4814
+ return records;
4815
+ }
4816
+ for (const entry of entries) {
4817
+ if (!entry.endsWith(".jsonl")) continue;
4818
+ const sessionId = entry.replace(/\.jsonl$/, "").replace(/_/g, ":");
4819
+ const loaded = await this.loadSession(sessionId);
4820
+ if (!loaded) continue;
4821
+ this.sessions.set(sessionId, loaded);
4822
+ records.set(sessionId, createNcpAgentSessionSummary(loaded.record));
4823
+ }
4824
+ return records;
4825
+ };
4826
+ upsertSummaryIndex = async (summary) => {
4827
+ (await this.loadSummaryIndex()).set(summary.sessionId, structuredClone(summary));
4828
+ await this.persistSummaryIndex();
4829
+ };
4830
+ upsertSummaryIndexForEvent = async (params) => {
4831
+ const { event, session, updatedAt } = params;
4832
+ const index = await this.loadSummaryIndex();
4833
+ const summary = upsertNcpAgentSessionSummaryEvent({
4834
+ current: index.get(session.sessionId),
4835
+ session,
4836
+ event,
4837
+ updatedAt
4838
+ });
4839
+ index.set(summary.sessionId, summary);
4840
+ await this.persistSummaryIndex();
4841
+ };
4842
+ removeSummaryIndex = async (sessionId) => {
4843
+ (await this.loadSummaryIndex()).delete(sessionId);
4844
+ await this.persistSummaryIndex();
4845
+ };
4846
+ persistSummaryIndex = async () => {
4847
+ const records = [...this.summaryIndex?.values() ?? []].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
4848
+ await this.ensureJournalDir();
4849
+ await writeFile(this.indexPath(), `${JSON.stringify({
4850
+ version: 1,
4851
+ records
4852
+ })}\n`, "utf-8");
4853
+ };
4854
+ ensureJournalDir = async () => {
4855
+ await mkdir(this.journalDir, { recursive: true });
4856
+ };
4857
+ sessionPath = (sessionId) => {
4858
+ return join(this.journalDir, `${safeNcpSessionFilename(sessionId.replace(/:/g, "_"))}.jsonl`);
4859
+ };
4860
+ indexPath = () => resolve(this.journalDir, NCP_AGENT_SESSION_JOURNAL_INDEX_FILE);
4284
4861
  };
4285
4862
  //#endregion
4286
4863
  //#region src/app/nextclaw-kernel.ts
@@ -4326,6 +4903,7 @@ var NextclawKernel = class {
4326
4903
  learningLoop;
4327
4904
  sessionLifecycleEvents;
4328
4905
  ncpAgentSessionStore;
4906
+ ncpAgentSessionJournalStore;
4329
4907
  startPromise = null;
4330
4908
  constructor(options = {}) {
4331
4909
  const sessionsDir = resolveKernelSessionsDir(options);
@@ -4336,7 +4914,11 @@ var NextclawKernel = class {
4336
4914
  sessionsDir,
4337
4915
  onSessionUpdated: this.publishSessionUpdated
4338
4916
  });
4339
- this.ncpAgentSessionStore = new NcpAgentSessionStoreAdapter(this.sessions, { onSessionUpdated: this.sessionSearch.handleSessionUpdated });
4917
+ this.ncpAgentSessionJournalStore = new NcpAgentSessionJournalStore(resolve(sessionsDir, ".ncp-agent-journal"));
4918
+ this.ncpAgentSessionStore = new NcpAgentSessionStoreAdapter(this.sessions, {
4919
+ journalStore: this.ncpAgentSessionJournalStore,
4920
+ onSessionUpdated: this.sessionSearch.handleSessionUpdated
4921
+ });
4340
4922
  this.sessionRequests = new SessionRequestManager({
4341
4923
  sessions: this.sessions,
4342
4924
  dispatcher: createAgentRuntimeSessionRequestDispatcher({
@@ -4363,6 +4945,7 @@ var NextclawKernel = class {
4363
4945
  this.ncpSessionApi = new NcpSessionApiService({
4364
4946
  eventBus: this.eventBus,
4365
4947
  getConfig: this.configManager.loadConfig,
4948
+ ncpAgentSessionStore: this.ncpAgentSessionStore,
4366
4949
  sessionManager: this.sessions
4367
4950
  });
4368
4951
  this.agentRuntimeManager = new AgentRuntimeManager({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/kernel",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "private": false,
5
5
  "description": "NextClaw product kernel skeleton for agents, tasks, sessions, context, tools, skills, providers, automation, and channels.",
6
6
  "type": "module",
@@ -20,19 +20,19 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "@nextclaw/mcp": "0.1.79",
24
- "@nextclaw/ncp-http-agent-server": "0.3.19",
25
- "@nextclaw/ncp-mcp": "0.1.81",
26
- "@nextclaw/core": "0.12.14",
27
- "@nextclaw/nextclaw-ncp-runtime-http-client": "0.1.6",
28
- "@nextclaw/nextclaw-hermes-acp-bridge": "0.1.6",
29
- "@nextclaw/openclaw-compat": "1.0.14",
30
- "@nextclaw/ncp-agent-runtime": "0.3.17",
31
- "@nextclaw/runtime": "0.2.46",
32
- "@nextclaw/shared": "0.1.1",
33
- "@nextclaw/ncp": "0.5.7",
34
- "@nextclaw/ncp-toolkit": "0.5.12",
35
- "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.1.7"
23
+ "@nextclaw/core": "0.12.16",
24
+ "@nextclaw/ncp-http-agent-server": "0.3.21",
25
+ "@nextclaw/mcp": "0.1.81",
26
+ "@nextclaw/ncp-agent-runtime": "0.3.19",
27
+ "@nextclaw/ncp-mcp": "0.1.83",
28
+ "@nextclaw/ncp-toolkit": "0.5.14",
29
+ "@nextclaw/nextclaw-ncp-runtime-http-client": "0.1.8",
30
+ "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.1.9",
31
+ "@nextclaw/nextclaw-hermes-acp-bridge": "0.1.8",
32
+ "@nextclaw/openclaw-compat": "1.0.16",
33
+ "@nextclaw/shared": "0.1.3",
34
+ "@nextclaw/runtime": "0.2.48",
35
+ "@nextclaw/ncp": "0.5.9"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.17.6",