@nextclaw/kernel 0.1.6 → 0.1.8

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 +2 -14
  2. package/dist/index.js +372 -199
  3. package/package.json +14 -14
package/dist/index.d.ts CHANGED
@@ -140,11 +140,6 @@ declare class ExtensionManager {
140
140
  getUiMetadata: () => PluginUiMetadata[];
141
141
  toConfigView: (config: Config) => Config;
142
142
  mergeConfigView: (current: Config, nextConfigView: Record<string, unknown>) => Config;
143
- resolveMessageToolHints: (params: {
144
- channel?: string | null;
145
- config?: Config;
146
- accountId?: string | null;
147
- }) => string[];
148
143
  }
149
144
  //#endregion
150
145
  //#region src/features/runtime-registry/services/agent-runtime-registry.service.d.ts
@@ -888,26 +883,19 @@ declare class NextclawNcpToolRegistry implements NcpToolRegistry {
888
883
  private buildToolExecutionContext;
889
884
  private registerDefaultTools;
890
885
  private registerMessagingTools;
886
+ private resolveMessageChannels;
891
887
  private registerExtensionTools;
892
888
  private registerTool;
893
889
  private registerAdditionalTools;
894
890
  private isToolAvailable;
895
891
  }
896
892
  declare function resolveAgentHandoffDepth(metadata: Record<string, unknown>): number;
897
- declare function readAccountIdForHints(metadata: Record<string, unknown>, sessionMetadata: Record<string, unknown>): string | undefined;
898
893
  //#endregion
899
894
  //#region src/features/native-runtime/services/nextclaw-ncp-context-builder.service.d.ts
900
- type MessageToolHintsResolver = (params: {
901
- sessionKey: string;
902
- channel: string;
903
- chatId: string;
904
- accountId?: string | null;
905
- }) => string[];
906
895
  type NextclawNcpContextBuilderOptions = {
907
896
  sessionManager: SessionManager;
908
897
  toolRegistry: NextclawNcpToolRegistry;
909
898
  getConfig: () => Config;
910
- resolveMessageToolHints?: MessageToolHintsResolver;
911
899
  assetStore?: LocalAssetStore | null;
912
900
  };
913
901
  declare class NextclawNcpContextBuilder implements NcpContextBuilder {
@@ -975,4 +963,4 @@ declare function createNcpSessionSummary(params: {
975
963
  declare function toNcpMessages(sessionId: string, messages: SessionMessage[]): NcpMessage[];
976
964
  declare function resolveLegacyEventType(message: SessionMessage): string;
977
965
  //#endregion
978
- export { AGENT_RUNTIME_SESSION_MESSAGE_INGRESS_TYPE, AgentId, AgentManager, AgentRecord, AgentRuntimeContribution, AgentRuntimeEndpoint, AgentRuntimeEntry, type AgentRuntimeHandle, AgentRuntimeManager, AgentRuntimeManagerOptions, AgentRuntimeProviderRegistration, AgentRuntimeRegistry, AgentRuntimeSessionMessageRequest, AgentRuntimeSessionRequestDispatcherOptions, AgentRuntimeSessionTypeDescribeParams, AgentRuntimeSessionTypeIcon, AgentRuntimeSessionTypeOption, AutomationId, AutomationManager, AutomationManagerOptions, BuiltinNarpRuntimeRegistrationService, ChannelId, ChannelManager, ChannelReplyRouterDispatchParams, ConfigManager, ConfigManagerOptions, ConfigManagerRuntimeHooks, ContextCompactionPendingWork, ContextCompactionPreflightBeginResult, ContextCompactionPreflightResult, ContextCompactionPreflightService, ContextWindowOwner, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_LEARNING_LOOP_TOOL_CALL_THRESHOLD, DirectPromptDispatchParams, ExtensionContributions, ExtensionManager, GatewayInboundLoopRuntime, InstallationKind, LEARNING_LOOP_DISABLED_METADATA_KEY, LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY, LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY, LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY, LEARNING_LOOP_REQUESTED_SKILLS, LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY, LearningLoopManager, LearningLoopManagerOptions, LearningLoopRuntimeConfig, LearningLoopSessionRequester, LlmProviderManager, LlmProviderRuntime, LlmUsageManager, LlmUsageManagerOptions, LlmUsageRecord, LlmUsageSnapshot, LlmUsageStore, LlmUsageStoreOptions, LlmUsageSummary, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NativeAgentRuntimeFactory, NativeAgentRuntimeFactoryOptions, NativeRuntimeFactory, NcpRunnerAgent, NcpSessionApiService, NcpSessionApiServiceOptions, NextclawExtensionRegistry, NextclawKernel, NextclawKernelOptions, NextclawNcpContextBuilder, NextclawNcpToolRegistry, PluginRuntimeRegistrationController, ProviderManagerNcpLLMApi, RunPromptOverNcpParams, SessionId, SkillId, SkillManager, SkillRecord, StreamPromptOverNcpParams, TaskId, ToolId, ToolManager, ToolRecord, UnsignedUpdateManifest, UpdateBlockReason, UpdateHostKind, UpdateManifest, UpdateManifestReader, UpdatePreferences, UpdateProgress, UpdateSnapshot, UpdateStatus, UpdateToolCallResult, buildLearningLoopTask, buildLlmUsageSummary, buildNcpUserMessage, buildUserMessageParts, createAgentRuntimeHandle, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createLlmUsageRecord, createNcpSessionSummary, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isReplyCapableChannel, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, readAccountIdForHints, readLearningLoopRuntimeConfig, resolveAgentHandoffDepth, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveLegacyEventType, runGatewayInboundLoop, runPromptOverNcp, sanitizeLlmUsage, serializeUnsignedUpdateManifest, streamPromptOverNcp, toNcpMessages, waitForAgentRuntimeSessionReply };
966
+ export { AGENT_RUNTIME_SESSION_MESSAGE_INGRESS_TYPE, AgentId, AgentManager, AgentRecord, AgentRuntimeContribution, AgentRuntimeEndpoint, AgentRuntimeEntry, type AgentRuntimeHandle, AgentRuntimeManager, AgentRuntimeManagerOptions, AgentRuntimeProviderRegistration, AgentRuntimeRegistry, AgentRuntimeSessionMessageRequest, AgentRuntimeSessionRequestDispatcherOptions, AgentRuntimeSessionTypeDescribeParams, AgentRuntimeSessionTypeIcon, AgentRuntimeSessionTypeOption, AutomationId, AutomationManager, AutomationManagerOptions, BuiltinNarpRuntimeRegistrationService, ChannelId, ChannelManager, ChannelReplyRouterDispatchParams, ConfigManager, ConfigManagerOptions, ConfigManagerRuntimeHooks, ContextCompactionPendingWork, ContextCompactionPreflightBeginResult, ContextCompactionPreflightResult, ContextCompactionPreflightService, ContextWindowOwner, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_LEARNING_LOOP_TOOL_CALL_THRESHOLD, DirectPromptDispatchParams, ExtensionContributions, ExtensionManager, GatewayInboundLoopRuntime, InstallationKind, LEARNING_LOOP_DISABLED_METADATA_KEY, LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY, LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY, LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY, LEARNING_LOOP_REQUESTED_SKILLS, LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY, LearningLoopManager, LearningLoopManagerOptions, LearningLoopRuntimeConfig, LearningLoopSessionRequester, LlmProviderManager, LlmProviderRuntime, LlmUsageManager, LlmUsageManagerOptions, LlmUsageRecord, LlmUsageSnapshot, LlmUsageStore, LlmUsageStoreOptions, LlmUsageSummary, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NativeAgentRuntimeFactory, NativeAgentRuntimeFactoryOptions, NativeRuntimeFactory, NcpRunnerAgent, NcpSessionApiService, NcpSessionApiServiceOptions, NextclawExtensionRegistry, NextclawKernel, NextclawKernelOptions, NextclawNcpContextBuilder, NextclawNcpToolRegistry, PluginRuntimeRegistrationController, ProviderManagerNcpLLMApi, RunPromptOverNcpParams, SessionId, SkillId, SkillManager, SkillRecord, StreamPromptOverNcpParams, TaskId, ToolId, ToolManager, ToolRecord, UnsignedUpdateManifest, UpdateBlockReason, UpdateHostKind, UpdateManifest, UpdateManifestReader, UpdatePreferences, UpdateProgress, UpdateSnapshot, UpdateStatus, UpdateToolCallResult, buildLearningLoopTask, buildLlmUsageSummary, buildNcpUserMessage, buildUserMessageParts, createAgentRuntimeHandle, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createLlmUsageRecord, createNcpSessionSummary, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isReplyCapableChannel, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, readLearningLoopRuntimeConfig, resolveAgentHandoffDepth, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveLegacyEventType, runGatewayInboundLoop, runPromptOverNcp, sanitizeLlmUsage, serializeUnsignedUpdateManifest, streamPromptOverNcp, toNcpMessages, waitForAgentRuntimeSessionReply };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t
2
2
  import { AgentRouteResolver, CONTEXT_COMPACTION_METADATA_KEY, ChannelManager, ChannelManager as ChannelManager$1, CommandRegistry, ContextBuilder, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_SESSION_SEARCH_LIMIT, DisposableStore, EditFileTool, ExecTool, ExtensionToolAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MessageBus, MessageTool, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SessionManager, SessionRequestManager, SessionSearchManager, SessionsHistoryTool, SessionsListTool, Tool, ToolRegistry, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildContextWindowSnapshot, buildMinimalSystemExecutionPrompt, buildReloadPlan, buildToolCatalogEntries, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createToolExecutionContext, createTypingStopControlMessage, diffConfigPaths, ensureDir, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getSessionsPath, getWorkspacePath, loadConfig, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readParentSessionId, readSessionProjectRoot, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveProviderRuntime, resolveSessionWorkspacePath, resolveThinkingLevel, toDisposable } from "@nextclaw/core";
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
- import { NcpEventType, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
5
+ import { NcpEventType, consumeNcpRunHandle, createNcpRunHandle, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
6
6
  import { DefaultNcpAgentBackend, DefaultNcpAgentConversationStateManager, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
7
7
  import { basename, delimiter, dirname, join, resolve } from "node:path";
8
8
  import { access, appendFile, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
@@ -13,7 +13,7 @@ import { StdioRuntimeConfigResolver, StdioRuntimeNcpAgentRuntime, probeStdioRunt
13
13
  import { buildHermesAcpBridgeLaunchEnv, isHermesAcpRuntimeConfig } from "@nextclaw/nextclaw-hermes-acp-bridge";
14
14
  import { McpRegistryService, McpServerLifecycleManager } from "@nextclaw/mcp";
15
15
  import { McpNcpToolRegistryAdapter } from "@nextclaw/ncp-mcp";
16
- import { getPluginChannelBindings, getPluginUiMetadataFromRegistry, mergePluginConfigView, resolvePluginChannelMessageToolHints, toPluginConfigView } from "@nextclaw/openclaw-compat";
16
+ import { getPluginChannelBindings, getPluginUiMetadataFromRegistry, mergePluginConfigView, toPluginConfigView } from "@nextclaw/openclaw-compat";
17
17
  //#region src/managers/agent.manager.ts
18
18
  var AgentManager = class {
19
19
  listAgents = () => {
@@ -2611,7 +2611,7 @@ var NextclawNcpToolRegistry = class {
2611
2611
  registerMessagingTools = (context) => {
2612
2612
  const { channel, chatId, metadata } = context;
2613
2613
  const accountId = readMetadataAccountId(metadata, {});
2614
- const messageTool = new MessageTool((message) => this.options.bus.publishOutbound(message));
2614
+ const messageTool = new MessageTool((message) => this.options.bus.publishOutbound(message), { resolveChannels: this.resolveMessageChannels });
2615
2615
  messageTool.setContext(channel, chatId, accountId ?? null);
2616
2616
  this.registerTool(messageTool);
2617
2617
  if (this.options.cronService) {
@@ -2620,6 +2620,10 @@ var NextclawNcpToolRegistry = class {
2620
2620
  this.registerTool(cronTool);
2621
2621
  }
2622
2622
  };
2623
+ resolveMessageChannels = () => {
2624
+ const channels = (this.options.getExtensionRegistry?.())?.channels ?? [];
2625
+ return [...new Set(channels.map((registration) => registration.channel.id.trim()).filter(Boolean))].sort();
2626
+ };
2623
2627
  registerExtensionTools = (context) => {
2624
2628
  const extensionRegistry = this.options.getExtensionRegistry?.();
2625
2629
  if (!extensionRegistry || extensionRegistry.tools.length === 0) return;
@@ -2658,9 +2662,6 @@ function resolveAgentHandoffDepth(metadata) {
2658
2662
  if (!Number.isFinite(rawDepth) || rawDepth < 0) return 0;
2659
2663
  return Math.trunc(rawDepth);
2660
2664
  }
2661
- function readAccountIdForHints(metadata, sessionMetadata) {
2662
- return readMetadataAccountId(metadata, sessionMetadata);
2663
- }
2664
2665
  //#endregion
2665
2666
  //#region src/features/native-runtime/services/nextclaw-ncp-context-builder.service.ts
2666
2667
  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, /(现在|此刻|当前时间|现在几点|几点了|今天|今晚|今早|今晨|明天|昨天|日期)/];
@@ -2820,7 +2821,6 @@ var NextclawNcpContextBuilder = class {
2820
2821
  workspace: effectiveWorkspace
2821
2822
  });
2822
2823
  return {
2823
- accountId: readAccountIdForHints(requestMetadata, session.metadata),
2824
2824
  channel,
2825
2825
  chatId,
2826
2826
  config,
@@ -2837,13 +2837,7 @@ var NextclawNcpContextBuilder = class {
2837
2837
  };
2838
2838
  };
2839
2839
  buildModelMessages = (input, _options, runContext) => {
2840
- const { accountId, channel, chatId, config, currentTurn, effectiveModel, effectiveWorkspace, profile, requestedSkills, runtimeThinking, session } = runContext;
2841
- const messageToolHints = this.options.resolveMessageToolHints?.({
2842
- sessionKey: input.sessionId,
2843
- channel,
2844
- chatId,
2845
- accountId: accountId ?? null
2846
- });
2840
+ const { channel, chatId, config, currentTurn, effectiveModel, effectiveWorkspace, profile, requestedSkills, runtimeThinking, session } = runContext;
2847
2841
  const toolDefinitions = this.options.toolRegistry.getToolDefinitions();
2848
2842
  const additionalSystemSections = [buildSessionOrchestrationSection(), buildMinimalSystemExecutionPrompt(effectiveModel)].filter(Boolean);
2849
2843
  const contextBuilder = new ContextBuilder(effectiveWorkspace, config.agents.context, {
@@ -2865,7 +2859,6 @@ var NextclawNcpContextBuilder = class {
2865
2859
  sessionKey: input.sessionId,
2866
2860
  thinkingLevel: runtimeThinking,
2867
2861
  skillNames: requestedSkills.selectors,
2868
- messageToolHints,
2869
2862
  availableTools: buildToolCatalogEntries(toolDefinitions),
2870
2863
  additionalSystemSections
2871
2864
  });
@@ -3034,11 +3027,6 @@ var NativeAgentRuntimeFactory = class {
3034
3027
  sessionManager: this.options.sessions,
3035
3028
  toolRegistry,
3036
3029
  getConfig: this.options.configManager.loadConfig,
3037
- resolveMessageToolHints: ({ channel, accountId }) => this.options.extensions.resolveMessageToolHints({
3038
- channel,
3039
- config: this.options.configManager.loadConfig(),
3040
- accountId
3041
- }),
3042
3030
  assetStore: this.options.assetStore
3043
3031
  }),
3044
3032
  llmApi: new ProviderManagerNcpLLMApi(this.observedProviderManager),
@@ -3087,11 +3075,6 @@ var NativeAgentRuntimeFactory = class {
3087
3075
  sessionManager: this.options.sessions,
3088
3076
  toolRegistry: this.createToolRegistry(this.options.providerManager),
3089
3077
  getConfig: this.options.configManager.loadConfig,
3090
- resolveMessageToolHints: ({ channel, accountId }) => this.options.extensions.resolveMessageToolHints({
3091
- channel,
3092
- config: this.options.configManager.loadConfig(),
3093
- accountId
3094
- }),
3095
3078
  assetStore: this.options.assetStore
3096
3079
  }).prepare(input).tools;
3097
3080
  };
@@ -3383,35 +3366,39 @@ var AgentRuntimeManager = class {
3383
3366
  }
3384
3367
  };
3385
3368
  };
3386
- createMaterializingAgentClientEndpoint = (backend) => ({
3387
- get manifest() {
3388
- return backend.manifest;
3389
- },
3390
- start: backend.start,
3391
- stop: backend.stop,
3392
- subscribe: backend.subscribe,
3393
- stream: async (payload) => {
3394
- await consumeAgentEvents(backend.stream(payload));
3395
- },
3396
- abort: backend.abort,
3397
- send: async (envelope) => {
3398
- await consumeAgentEvents(backend.send(this.materializeAgentSendEnvelope(envelope)));
3399
- },
3400
- emit: async (event) => {
3401
- switch (event.type) {
3402
- case NcpEventType.MessageRequest:
3403
- await consumeAgentEvents(backend.send(this.materializeAgentSendEnvelope(event.payload)));
3404
- return;
3405
- case NcpEventType.MessageStreamRequest:
3406
- await consumeAgentEvents(backend.stream(event.payload));
3407
- return;
3408
- case NcpEventType.MessageAbort:
3409
- await backend.abort(event.payload);
3410
- return;
3411
- default: await backend.emit(event);
3369
+ createMaterializingAgentClientEndpoint = (backend) => {
3370
+ const send = async (envelope) => {
3371
+ const requestEnvelope = this.materializeAgentSendEnvelope(envelope);
3372
+ return await consumeNcpRunHandle(backend.send(requestEnvelope), createNcpRunHandle(requestEnvelope));
3373
+ };
3374
+ return {
3375
+ get manifest() {
3376
+ return backend.manifest;
3377
+ },
3378
+ start: backend.start,
3379
+ stop: backend.stop,
3380
+ subscribe: backend.subscribe,
3381
+ stream: async (payload) => {
3382
+ await consumeAgentEvents(backend.stream(payload));
3383
+ },
3384
+ abort: backend.abort,
3385
+ send,
3386
+ emit: async (event) => {
3387
+ switch (event.type) {
3388
+ case NcpEventType.MessageRequest:
3389
+ await send(event.payload);
3390
+ return;
3391
+ case NcpEventType.MessageStreamRequest:
3392
+ await consumeAgentEvents(backend.stream(event.payload));
3393
+ return;
3394
+ case NcpEventType.MessageAbort:
3395
+ await backend.abort(event.payload);
3396
+ return;
3397
+ default: await backend.emit(event);
3398
+ }
3412
3399
  }
3413
- }
3414
- });
3400
+ };
3401
+ };
3415
3402
  warmDerivedCapabilities = async () => {
3416
3403
  this.assertNotDisposed();
3417
3404
  this.warmupPromise ??= this.runDerivedCapabilityWarmup();
@@ -3665,15 +3652,6 @@ var ExtensionManager = class {
3665
3652
  getUiMetadata = () => this.uiMetadata;
3666
3653
  toConfigView = (config) => toPluginConfigView(config, this.channelBindings);
3667
3654
  mergeConfigView = (current, nextConfigView) => mergePluginConfigView(current, nextConfigView, this.channelBindings);
3668
- resolveMessageToolHints = (params) => {
3669
- if (!this.pluginRegistry) return [];
3670
- return resolvePluginChannelMessageToolHints({
3671
- registry: this.pluginRegistry,
3672
- channel: params.channel,
3673
- cfg: params.config,
3674
- accountId: params.accountId
3675
- });
3676
- };
3677
3655
  };
3678
3656
  //#endregion
3679
3657
  //#region src/configs/ncp-lifecycle-event.config.ts
@@ -4285,8 +4263,8 @@ var NcpSessionApiService = class {
4285
4263
  if (normalizedSessionId && this.options.ncpAgentSessionStore) {
4286
4264
  const existing = await this.options.ncpAgentSessionStore.getSession(normalizedSessionId);
4287
4265
  if (existing) {
4288
- await this.options.ncpAgentSessionStore.replaceSession({
4289
- ...existing,
4266
+ await this.options.ncpAgentSessionStore.updateSessionMetadata({
4267
+ sessionId: normalizedSessionId,
4290
4268
  metadata: buildUpdatedMetadata({
4291
4269
  existingMetadata: existing.metadata,
4292
4270
  patch
@@ -4349,21 +4327,6 @@ var NcpAgentLegacySessionStore = class {
4349
4327
  metadata: structuredClone(session.metadata)
4350
4328
  };
4351
4329
  };
4352
- getSessionSummary = (sessionId) => {
4353
- const session = this.getSession(sessionId);
4354
- if (!session) return null;
4355
- const lastMessageAt = session.messages.at(-1)?.timestamp;
4356
- return {
4357
- sessionId: session.sessionId,
4358
- ...session.agentId ? { agentId: session.agentId } : {},
4359
- messageCount: session.messages.length,
4360
- createdAt: session.createdAt,
4361
- updatedAt: session.updatedAt,
4362
- ...lastMessageAt ? { lastMessageAt } : {},
4363
- status: "idle",
4364
- metadata: session.metadata
4365
- };
4366
- };
4367
4330
  listSessionMessages = (sessionId) => this.getSession(sessionId)?.messages ?? [];
4368
4331
  listSessionSummaries = () => this.sessionManager.listSessions().map((record) => ({
4369
4332
  sessionId: record.key,
@@ -4376,7 +4339,16 @@ var NcpAgentLegacySessionStore = class {
4376
4339
  metadata: structuredClone(record.metadata)
4377
4340
  }));
4378
4341
  saveSession = (sessionRecord) => this.persistSession(sessionRecord, true);
4379
- replaceSession = (sessionRecord) => this.persistSession(sessionRecord, false);
4342
+ updateSessionMetadata = async (params) => {
4343
+ const { metadata, sessionId, updatedAt } = params;
4344
+ const session = this.sessionManager.getIfExists(sessionId);
4345
+ if (!session) return false;
4346
+ session.metadata = structuredClone(metadata);
4347
+ session.updatedAt = new Date(ensureIsoTimestamp(updatedAt, session.updatedAt.toISOString()));
4348
+ this.sessionManager.save(session);
4349
+ this.options.onSessionUpdated?.(sessionId);
4350
+ return true;
4351
+ };
4380
4352
  deleteSession = (sessionId) => {
4381
4353
  const existing = this.getSession(sessionId);
4382
4354
  if (!existing) return null;
@@ -4422,14 +4394,13 @@ var NcpAgentSessionStoreAdapter = class {
4422
4394
  const { event, session } = params;
4423
4395
  if (!await journalStore.hasSession(session.sessionId)) {
4424
4396
  const legacySession = this.legacyStore.getSession(session.sessionId);
4425
- if (legacySession) await journalStore.replaceSession(legacySession);
4397
+ if (legacySession) await journalStore.materializeSession(legacySession);
4426
4398
  }
4427
4399
  await journalStore.appendSessionEvent(params);
4428
4400
  if (isSessionSummaryRefreshEvent(event)) this.options.onSessionUpdated?.(session.sessionId);
4429
4401
  };
4430
4402
  }
4431
4403
  getSession = async (sessionId) => await this.options.journalStore?.getSession(sessionId) ?? this.legacyStore.getSession(sessionId);
4432
- getSessionSummary = async (sessionId) => await this.options.journalStore?.getSessionSummary(sessionId) ?? this.legacyStore.getSessionSummary(sessionId);
4433
4404
  listSessionMessages = async (sessionId) => {
4434
4405
  const journalStore = this.options.journalStore;
4435
4406
  return journalStore && await journalStore.hasSession(sessionId) ? await journalStore.listSessionMessages(sessionId) : this.legacyStore.listSessionMessages(sessionId);
@@ -4456,20 +4427,20 @@ var NcpAgentSessionStoreAdapter = class {
4456
4427
  saveSession = async (sessionRecord) => {
4457
4428
  const journalStore = this.options.journalStore;
4458
4429
  if (journalStore && await journalStore.hasSession(sessionRecord.sessionId)) {
4459
- await journalStore.replaceSession(sessionRecord);
4430
+ await journalStore.materializeSession(sessionRecord);
4460
4431
  this.options.onSessionUpdated?.(sessionRecord.sessionId);
4461
4432
  return;
4462
4433
  }
4463
4434
  await this.legacyStore.saveSession(sessionRecord);
4464
4435
  };
4465
- replaceSession = async (sessionRecord) => {
4436
+ updateSessionMetadata = async (params) => {
4466
4437
  const journalStore = this.options.journalStore;
4467
- if (journalStore && await journalStore.hasSession(sessionRecord.sessionId)) {
4468
- await journalStore.replaceSession(sessionRecord);
4469
- this.options.onSessionUpdated?.(sessionRecord.sessionId);
4470
- return;
4438
+ if (journalStore && await journalStore.hasSession(params.sessionId)) {
4439
+ const updated = await journalStore.updateSessionMetadata(params);
4440
+ if (updated) this.options.onSessionUpdated?.(params.sessionId);
4441
+ return updated;
4471
4442
  }
4472
- await this.legacyStore.replaceSession(sessionRecord);
4443
+ return this.legacyStore.updateSessionMetadata(params);
4473
4444
  };
4474
4445
  deleteSession = async (sessionId) => {
4475
4446
  const journalSession = await this.options.journalStore?.deleteSession(sessionId);
@@ -4493,6 +4464,7 @@ function isSessionSummaryRefreshEvent(event) {
4493
4464
  }
4494
4465
  const NCP_AGENT_SESSION_JOURNAL_INDEX_FILE = ".ncp-agent-session-index.json";
4495
4466
  const AUTO_SESSION_LABEL_MAX_LENGTH = 64;
4467
+ const NCP_AGENT_SESSION_SNAPSHOT_MESSAGE_EVENT_TYPE = "session.snapshot.message";
4496
4468
  function normalizeNcpSessionId(sessionId) {
4497
4469
  return sessionId.trim();
4498
4470
  }
@@ -4500,15 +4472,13 @@ function safeNcpSessionFilename(value) {
4500
4472
  return value.replace(/[^a-zA-Z0-9._-]/g, "_");
4501
4473
  }
4502
4474
  function normalizeNcpAgentId(agentId) {
4503
- const normalized = agentId?.trim().toLowerCase();
4504
- return normalized ? normalized : void 0;
4475
+ return agentId?.trim().toLowerCase() || void 0;
4505
4476
  }
4506
4477
  function isRecord$1(value) {
4507
4478
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4508
4479
  }
4509
4480
  function toIsoString(value, fallback) {
4510
- if (typeof value !== "string") return fallback;
4511
- const parsed = Date.parse(value);
4481
+ const parsed = typeof value === "string" ? Date.parse(value) : NaN;
4512
4482
  return Number.isFinite(parsed) ? new Date(parsed).toISOString() : fallback;
4513
4483
  }
4514
4484
  function createNcpAgentSessionSummary(record) {
@@ -4543,9 +4513,9 @@ function upsertNcpAgentSessionSummaryEvent(params) {
4543
4513
  ...current?.metadata ? structuredClone(current.metadata) : {},
4544
4514
  ...session.metadata ? structuredClone(session.metadata) : {}
4545
4515
  };
4546
- const label = readOptionalText(metadata.label) ?? readSummaryLabelFromEvent(event);
4547
- if (label) metadata.label = truncateLabel(label);
4548
4516
  const eventMessage = readMessageFromSummaryEvent(event);
4517
+ const label = readOptionalText(metadata.label) ?? (eventMessage?.role === "user" ? resolveAutoSessionLabel([eventMessage]) : null);
4518
+ if (label) metadata.label = truncateLabel(label);
4549
4519
  const messageCount = current ? current.messageCount + (eventMessage ? 1 : 0) : eventMessage ? 1 : 0;
4550
4520
  const lastMessageAt = readMessageTimestamp(eventMessage) ?? current?.lastMessageAt;
4551
4521
  return {
@@ -4561,24 +4531,32 @@ function upsertNcpAgentSessionSummaryEvent(params) {
4561
4531
  }
4562
4532
  async function replayNcpAgentSessionEvents(events) {
4563
4533
  const stateManager = new DefaultNcpAgentConversationStateManager();
4564
- for (const event of events) await stateManager.dispatch(event.type === NcpEventType.MessageCompleted ? createCompletedMessageEvent(event) : structuredClone(event));
4534
+ for (const event of events) await stateManager.dispatch(createReplayEvent(event));
4565
4535
  const snapshot = stateManager.getSnapshot();
4566
4536
  return [...snapshot.messages.map((message) => structuredClone(message)), ...snapshot.streamingMessage ? [structuredClone(snapshot.streamingMessage)] : []];
4567
4537
  }
4538
+ function createReplayEvent(event) {
4539
+ const replayEvent = structuredClone(event);
4540
+ const replayMessage = readMessageFromSummaryEvent(replayEvent);
4541
+ if (replayMessage?.role === "assistant" && (replayMessage.status === "pending" || replayMessage.status === "streaming")) replayMessage.status = "final";
4542
+ if (replayEvent.type === "session.snapshot.message" || replayEvent.type === NcpEventType.MessageCompleted) return {
4543
+ type: NcpEventType.MessageSent,
4544
+ payload: replayEvent.payload
4545
+ };
4546
+ return replayEvent;
4547
+ }
4568
4548
  function readNcpSessionSummaryActivityAt(summary) {
4569
4549
  return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
4570
4550
  }
4571
4551
  function readMessageTimestamp(message) {
4572
- return typeof message?.timestamp === "string" && Number.isFinite(Date.parse(message.timestamp)) ? new Date(message.timestamp).toISOString() : void 0;
4552
+ return toIsoString(message?.timestamp, "") || void 0;
4573
4553
  }
4574
4554
  function truncateLabel(value) {
4575
4555
  const chars = Array.from(value);
4576
4556
  return chars.length <= AUTO_SESSION_LABEL_MAX_LENGTH ? value : `${chars.slice(0, AUTO_SESSION_LABEL_MAX_LENGTH).join("")}...`;
4577
4557
  }
4578
4558
  function readOptionalText(value) {
4579
- if (typeof value !== "string") return null;
4580
- const trimmed = value.trim();
4581
- return trimmed ? trimmed : null;
4559
+ return (typeof value === "string" ? value.trim() : "") || null;
4582
4560
  }
4583
4561
  function resolveAutoSessionLabel(messages) {
4584
4562
  for (const message of messages) {
@@ -4594,34 +4572,139 @@ function readMessageFromSummaryEvent(event) {
4594
4572
  switch (event.type) {
4595
4573
  case NcpEventType.MessageSent: return event.payload.message;
4596
4574
  case NcpEventType.MessageCompleted: return event.payload.message;
4575
+ case NCP_AGENT_SESSION_SNAPSHOT_MESSAGE_EVENT_TYPE: return event.payload.message;
4597
4576
  default: return;
4598
4577
  }
4599
4578
  }
4600
- function readSummaryLabelFromEvent(event) {
4601
- const message = readMessageFromSummaryEvent(event);
4602
- if (message?.role !== "user") return null;
4603
- return resolveAutoSessionLabel([message]);
4604
- }
4605
- function createCompletedMessageEvent(event) {
4606
- return {
4607
- type: NcpEventType.MessageSent,
4608
- payload: {
4609
- sessionId: event.payload.sessionId,
4610
- message: structuredClone(event.payload.message),
4611
- ...event.payload.correlationId ? { correlationId: event.payload.correlationId } : {},
4612
- metadata: event.payload.metadata
4579
+ //#endregion
4580
+ //#region src/stores/ncp-agent-session-metadata.store.ts
4581
+ var NcpAgentSessionMetadataStore = class {
4582
+ constructor(journalDir) {
4583
+ this.journalDir = journalDir;
4584
+ }
4585
+ write = async (record) => {
4586
+ await mkdir(this.journalDir, { recursive: true });
4587
+ await writeFile(this.metadataPath(record.sessionId), `${this.serializeMetadata(record)}\n`, "utf-8");
4588
+ };
4589
+ read = async (sessionId, fallback) => {
4590
+ try {
4591
+ const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
4592
+ if (!isRecord$1(parsed) || parsed._type !== "metadata") return fallback;
4593
+ const createdAt = toIsoString(parsed.created_at, fallback.createdAt);
4594
+ const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
4595
+ return {
4596
+ metadata: isRecord$1(parsed.metadata) ? structuredClone(parsed.metadata) : {},
4597
+ ...agentId ? { agentId } : {},
4598
+ createdAt,
4599
+ updatedAt: toIsoString(parsed.updated_at, createdAt)
4600
+ };
4601
+ } catch {
4602
+ return fallback;
4613
4603
  }
4614
4604
  };
4615
- }
4605
+ delete = async (sessionId) => {
4606
+ try {
4607
+ await unlink(this.metadataPath(sessionId));
4608
+ } catch {}
4609
+ };
4610
+ serializeMetadata = (record) => {
4611
+ const serialized = JSON.stringify(createNcpAgentSessionJournalMetadataEntry(record));
4612
+ if (!serialized) throw new Error("ncp agent session metadata serialization produced empty output");
4613
+ return serialized;
4614
+ };
4615
+ metadataPath = (sessionId) => {
4616
+ return join(this.journalDir, `${safeNcpSessionFilename(sessionId.replace(/:/g, "_"))}.metadata.json`);
4617
+ };
4618
+ };
4619
+ //#endregion
4620
+ //#region src/stores/ncp-agent-session-summary-index.store.ts
4621
+ var NcpAgentSessionSummaryIndexStore = class {
4622
+ summaryIndex = null;
4623
+ constructor(journalDir, loadSession) {
4624
+ this.journalDir = journalDir;
4625
+ this.loadSession = loadSession;
4626
+ }
4627
+ has = async (sessionId) => (await this.load()).has(sessionId);
4628
+ list = async () => {
4629
+ return [...(await this.load()).values()].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
4630
+ };
4631
+ upsert = async (summary) => {
4632
+ (await this.load()).set(summary.sessionId, structuredClone(summary));
4633
+ await this.persist();
4634
+ };
4635
+ upsertForEvent = async (params) => {
4636
+ const { event, session, updatedAt } = params;
4637
+ const index = await this.load();
4638
+ const summary = upsertNcpAgentSessionSummaryEvent({
4639
+ current: index.get(session.sessionId),
4640
+ session,
4641
+ event,
4642
+ updatedAt
4643
+ });
4644
+ index.set(summary.sessionId, summary);
4645
+ await this.persist();
4646
+ };
4647
+ remove = async (sessionId) => {
4648
+ (await this.load()).delete(sessionId);
4649
+ await this.persist();
4650
+ };
4651
+ load = async () => {
4652
+ if (this.summaryIndex) return this.summaryIndex;
4653
+ try {
4654
+ const parsed = JSON.parse(await readFile(this.indexPath(), "utf-8"));
4655
+ if (parsed.version === 1 && Array.isArray(parsed.records)) {
4656
+ this.summaryIndex = new Map(parsed.records.map((record) => [record.sessionId, structuredClone(record)]));
4657
+ return this.summaryIndex;
4658
+ }
4659
+ } catch {}
4660
+ this.summaryIndex = await this.rebuild();
4661
+ await this.persist();
4662
+ return this.summaryIndex;
4663
+ };
4664
+ rebuild = async () => {
4665
+ const records = /* @__PURE__ */ new Map();
4666
+ let entries = [];
4667
+ try {
4668
+ entries = await readdir(this.journalDir);
4669
+ } catch {
4670
+ return records;
4671
+ }
4672
+ for (const entry of entries) {
4673
+ if (!entry.endsWith(".jsonl")) continue;
4674
+ const sessionId = entry.replace(/\.jsonl$/, "").replace(/_/g, ":");
4675
+ const loaded = await this.loadSession(sessionId);
4676
+ if (loaded) records.set(sessionId, createNcpAgentSessionSummary(loaded.record));
4677
+ }
4678
+ return records;
4679
+ };
4680
+ persist = async () => {
4681
+ const records = [...this.summaryIndex?.values() ?? []].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
4682
+ await mkdir(this.journalDir, { recursive: true });
4683
+ await writeFile(this.indexPath(), `${JSON.stringify({
4684
+ version: 1,
4685
+ records
4686
+ })}\n`, "utf-8");
4687
+ };
4688
+ indexPath = () => resolve(this.journalDir, NCP_AGENT_SESSION_JOURNAL_INDEX_FILE);
4689
+ };
4616
4690
  //#endregion
4617
4691
  //#region src/stores/ncp-agent-session-journal.store.ts
4692
+ function serializeJournalEntry(entry) {
4693
+ const serialized = JSON.stringify(entry);
4694
+ if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
4695
+ if (!isRecord$1(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
4696
+ return serialized;
4697
+ }
4618
4698
  var NcpAgentSessionJournalStore = class {
4619
4699
  sessions = /* @__PURE__ */ new Map();
4620
4700
  nextSeqBySession = /* @__PURE__ */ new Map();
4621
4701
  writeChains = /* @__PURE__ */ new Map();
4622
- summaryIndex = null;
4702
+ metadataStore;
4703
+ summaryIndexStore;
4623
4704
  constructor(journalDir) {
4624
4705
  this.journalDir = journalDir;
4706
+ this.metadataStore = new NcpAgentSessionMetadataStore(journalDir);
4707
+ this.summaryIndexStore = new NcpAgentSessionSummaryIndexStore(journalDir, async (sessionId) => this.loadSession(sessionId));
4625
4708
  }
4626
4709
  appendSessionEvent = async (params) => {
4627
4710
  const sessionId = normalizeNcpSessionId(params.session.sessionId);
@@ -4646,22 +4729,42 @@ var NcpAgentSessionJournalStore = class {
4646
4729
  this.sessions.set(normalizedSessionId, loaded);
4647
4730
  return structuredClone(loaded.record);
4648
4731
  };
4649
- getSessionSummary = async (sessionId) => {
4650
- const normalizedSessionId = normalizeNcpSessionId(sessionId);
4651
- if (!normalizedSessionId) return null;
4652
- const indexed = (await this.loadSummaryIndex()).get(normalizedSessionId);
4653
- if (indexed) return structuredClone(indexed);
4654
- const record = await this.getSession(normalizedSessionId);
4655
- return record ? createNcpAgentSessionSummary(record) : null;
4656
- };
4657
4732
  listSessionSummaries = async () => {
4658
- return [...(await this.loadSummaryIndex()).values()].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
4733
+ return this.summaryIndexStore.list();
4659
4734
  };
4660
4735
  listSessionMessages = async (sessionId) => {
4661
4736
  const session = await this.getSession(sessionId);
4662
4737
  return session ? session.messages.map((message) => structuredClone(message)) : [];
4663
4738
  };
4664
- replaceSession = async (record) => {
4739
+ updateSessionMetadata = async (params) => {
4740
+ const sessionId = normalizeNcpSessionId(params.sessionId);
4741
+ if (!sessionId) return false;
4742
+ const next = (this.writeChains.get(sessionId) ?? Promise.resolve()).then(() => this.updateSessionMetadataNow({
4743
+ ...params,
4744
+ sessionId
4745
+ }));
4746
+ this.writeChains.set(sessionId, next.then(() => void 0, () => void 0));
4747
+ return await next;
4748
+ };
4749
+ updateSessionMetadataNow = async (params) => {
4750
+ const { metadata, sessionId, updatedAt } = params;
4751
+ const loaded = this.sessions.get(sessionId) ?? await this.loadSession(sessionId);
4752
+ if (!loaded) return false;
4753
+ const nextRecord = {
4754
+ ...loaded.record,
4755
+ metadata: structuredClone(metadata),
4756
+ updatedAt
4757
+ };
4758
+ await this.metadataStore.write(nextRecord);
4759
+ this.sessions.set(sessionId, {
4760
+ record: nextRecord,
4761
+ nextSeq: loaded.nextSeq
4762
+ });
4763
+ this.nextSeqBySession.set(sessionId, loaded.nextSeq);
4764
+ await this.summaryIndexStore.upsert(createNcpAgentSessionSummary(nextRecord));
4765
+ return true;
4766
+ };
4767
+ materializeSession = async (record) => {
4665
4768
  const sessionId = normalizeNcpSessionId(record.sessionId);
4666
4769
  if (!sessionId) return;
4667
4770
  await this.ensureJournalDir();
@@ -4669,27 +4772,28 @@ var NcpAgentSessionJournalStore = class {
4669
4772
  ...record,
4670
4773
  sessionId
4671
4774
  });
4672
- const entries = [createNcpAgentSessionJournalMetadataEntry(nextRecord), ...nextRecord.messages.map((message, index) => ({
4775
+ await this.metadataStore.write(nextRecord);
4776
+ const entries = nextRecord.messages.map((message, index) => ({
4673
4777
  _type: "event",
4674
4778
  version: 1,
4675
4779
  seq: index + 1,
4676
4780
  timestamp: message.timestamp ?? nextRecord.updatedAt,
4677
4781
  event: {
4678
- type: NcpEventType.MessageSent,
4782
+ type: NCP_AGENT_SESSION_SNAPSHOT_MESSAGE_EVENT_TYPE,
4679
4783
  payload: {
4680
4784
  sessionId,
4681
4785
  message: structuredClone(message)
4682
4786
  }
4683
4787
  }
4684
- }))];
4685
- await writeFile(this.sessionPath(sessionId), `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
4788
+ }));
4789
+ await writeFile(this.sessionPath(sessionId), entries.length > 0 ? `${entries.map(serializeJournalEntry).join("\n")}\n` : "", "utf-8");
4686
4790
  const nextSeq = nextRecord.messages.length + 1;
4687
4791
  this.sessions.set(sessionId, {
4688
4792
  record: nextRecord,
4689
4793
  nextSeq
4690
4794
  });
4691
4795
  this.nextSeqBySession.set(sessionId, nextSeq);
4692
- await this.upsertSummaryIndex(createNcpAgentSessionSummary(nextRecord));
4796
+ await this.summaryIndexStore.upsert(createNcpAgentSessionSummary(nextRecord));
4693
4797
  };
4694
4798
  deleteSession = async (sessionId) => {
4695
4799
  const normalizedSessionId = normalizeNcpSessionId(sessionId);
@@ -4700,23 +4804,31 @@ var NcpAgentSessionJournalStore = class {
4700
4804
  try {
4701
4805
  await unlink(this.sessionPath(normalizedSessionId));
4702
4806
  } catch {}
4703
- await this.removeSummaryIndex(normalizedSessionId);
4807
+ await this.metadataStore.delete(normalizedSessionId);
4808
+ await this.summaryIndexStore.remove(normalizedSessionId);
4704
4809
  return existing;
4705
4810
  };
4706
4811
  hasSession = async (sessionId) => {
4707
4812
  const normalizedSessionId = normalizeNcpSessionId(sessionId);
4708
4813
  if (!normalizedSessionId) return false;
4709
- return (await this.loadSummaryIndex()).has(normalizedSessionId);
4814
+ return this.summaryIndexStore.has(normalizedSessionId);
4710
4815
  };
4711
4816
  appendSessionEventNow = async (params) => {
4712
4817
  const { event, session, updatedAt } = params;
4713
4818
  const { sessionId } = session;
4714
4819
  await this.ensureJournalDir();
4715
4820
  const existing = this.sessions.get(sessionId) ?? (this.nextSeqBySession.has(sessionId) ? null : await this.loadSession(sessionId));
4716
- const hasJournal = Boolean(existing) || this.nextSeqBySession.has(sessionId);
4717
4821
  const nextSeq = this.nextSeqBySession.get(sessionId) ?? existing?.nextSeq ?? 1;
4718
4822
  const path = this.sessionPath(sessionId);
4719
- if (!hasJournal) await appendFile(path, `${JSON.stringify(createNcpAgentSessionJournalMetadataEntry(session))}\n`, "utf-8");
4823
+ await this.metadataStore.write({
4824
+ ...session,
4825
+ ...existing?.record.createdAt ? { createdAt: existing.record.createdAt } : {},
4826
+ updatedAt,
4827
+ metadata: {
4828
+ ...existing?.record.metadata ? structuredClone(existing.record.metadata) : {},
4829
+ ...session.metadata ? structuredClone(session.metadata) : {}
4830
+ }
4831
+ });
4720
4832
  const entry = {
4721
4833
  _type: "event",
4722
4834
  version: 1,
@@ -4724,10 +4836,10 @@ var NcpAgentSessionJournalStore = class {
4724
4836
  timestamp: updatedAt,
4725
4837
  event: structuredClone(event)
4726
4838
  };
4727
- await appendFile(path, `${JSON.stringify(entry)}\n`, "utf-8");
4839
+ await this.appendJournalEntry(path, entry);
4728
4840
  this.nextSeqBySession.set(sessionId, nextSeq + 1);
4729
4841
  this.sessions.delete(sessionId);
4730
- await this.upsertSummaryIndexForEvent({
4842
+ await this.summaryIndexStore.upsertForEvent({
4731
4843
  session,
4732
4844
  event,
4733
4845
  updatedAt
@@ -4740,7 +4852,9 @@ var NcpAgentSessionJournalStore = class {
4740
4852
  } catch {
4741
4853
  return null;
4742
4854
  }
4743
- const { agentId, createdAt, events, metadata, nextSeq, updatedAt } = this.parseSessionJournal(raw);
4855
+ const parsedJournal = this.parseSessionJournal(raw);
4856
+ const { agentId, createdAt, updatedAt, metadata } = await this.metadataStore.read(sessionId, parsedJournal);
4857
+ const { events, nextSeq } = parsedJournal;
4744
4858
  const messages = await replayNcpAgentSessionEvents(events);
4745
4859
  const record = {
4746
4860
  sessionId,
@@ -4763,9 +4877,15 @@ var NcpAgentSessionJournalStore = class {
4763
4877
  let updatedAt = createdAt;
4764
4878
  let nextSeq = 1;
4765
4879
  const events = [];
4766
- for (const line of raw.split("\n")) {
4880
+ for (const [index, line] of raw.split("\n").entries()) {
4767
4881
  if (!line.trim()) continue;
4768
- const parsed = JSON.parse(line);
4882
+ let parsed;
4883
+ try {
4884
+ parsed = JSON.parse(line);
4885
+ } catch (error) {
4886
+ console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
4887
+ continue;
4888
+ }
4769
4889
  if (!isRecord$1(parsed)) continue;
4770
4890
  if (parsed._type === "metadata") {
4771
4891
  metadata = isRecord$1(parsed.metadata) ? structuredClone(parsed.metadata) : {};
@@ -4790,72 +4910,125 @@ var NcpAgentSessionJournalStore = class {
4790
4910
  events
4791
4911
  };
4792
4912
  };
4793
- loadSummaryIndex = async () => {
4794
- if (this.summaryIndex) return this.summaryIndex;
4795
- try {
4796
- const parsed = JSON.parse(await readFile(this.indexPath(), "utf-8"));
4797
- if (parsed.version === 1 && Array.isArray(parsed.records)) {
4798
- this.summaryIndex = new Map(parsed.records.map((record) => [record.sessionId, structuredClone(record)]));
4799
- return this.summaryIndex;
4800
- }
4801
- } catch {}
4802
- this.summaryIndex = await this.rebuildSummaryIndex();
4803
- await this.persistSummaryIndex();
4804
- return this.summaryIndex;
4913
+ appendJournalEntry = async (path, entry) => {
4914
+ await appendFile(path, `${serializeJournalEntry(entry)}\n`, "utf-8");
4805
4915
  };
4806
- rebuildSummaryIndex = async () => {
4807
- const records = /* @__PURE__ */ new Map();
4808
- let entries = [];
4809
- try {
4810
- entries = await readdir(this.journalDir);
4811
- } catch {
4812
- return records;
4813
- }
4814
- for (const entry of entries) {
4815
- if (!entry.endsWith(".jsonl")) continue;
4816
- const sessionId = entry.replace(/\.jsonl$/, "").replace(/_/g, ":");
4817
- const loaded = await this.loadSession(sessionId);
4818
- if (!loaded) continue;
4819
- this.sessions.set(sessionId, loaded);
4820
- records.set(sessionId, createNcpAgentSessionSummary(loaded.record));
4821
- }
4822
- return records;
4916
+ ensureJournalDir = async () => {
4917
+ await mkdir(this.journalDir, { recursive: true });
4823
4918
  };
4824
- upsertSummaryIndex = async (summary) => {
4825
- (await this.loadSummaryIndex()).set(summary.sessionId, structuredClone(summary));
4826
- await this.persistSummaryIndex();
4919
+ sessionPath = (sessionId) => {
4920
+ return join(this.journalDir, `${safeNcpSessionFilename(sessionId.replace(/:/g, "_"))}.jsonl`);
4827
4921
  };
4828
- upsertSummaryIndexForEvent = async (params) => {
4829
- const { event, session, updatedAt } = params;
4830
- const index = await this.loadSummaryIndex();
4831
- const summary = upsertNcpAgentSessionSummaryEvent({
4832
- current: index.get(session.sessionId),
4833
- session,
4834
- event,
4835
- updatedAt
4836
- });
4837
- index.set(summary.sessionId, summary);
4838
- await this.persistSummaryIndex();
4922
+ };
4923
+ //#endregion
4924
+ //#region src/contributions/session-context-window/index.ts
4925
+ const STREAM_REFRESH_DELAY_MS = 1500;
4926
+ function formatBackgroundError$1(error) {
4927
+ if (error instanceof Error) return error.stack ?? error.message;
4928
+ return String(error);
4929
+ }
4930
+ function readEventSessionId(event) {
4931
+ const payload = "payload" in event ? event.payload : null;
4932
+ if (!payload || typeof payload !== "object") return null;
4933
+ return "sessionId" in payload && typeof payload.sessionId === "string" ? payload.sessionId.trim() || null : null;
4934
+ }
4935
+ function isContextWindow(value) {
4936
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4937
+ }
4938
+ function createSignature(value) {
4939
+ return JSON.stringify(value);
4940
+ }
4941
+ function shouldRefreshDuringStream(event) {
4942
+ switch (event.type) {
4943
+ case NcpEventType.MessageTextDelta:
4944
+ case NcpEventType.MessageReasoningDelta:
4945
+ case NcpEventType.MessageToolCallArgsDelta:
4946
+ case NcpEventType.MessageToolCallResult: return true;
4947
+ default: return false;
4948
+ }
4949
+ }
4950
+ function shouldRefreshImmediately(event) {
4951
+ switch (event.type) {
4952
+ case NcpEventType.MessageCompleted:
4953
+ case NcpEventType.MessageFailed:
4954
+ case NcpEventType.MessageAbort:
4955
+ case NcpEventType.RunFinished:
4956
+ case NcpEventType.RunError: return true;
4957
+ default: return false;
4958
+ }
4959
+ }
4960
+ var SessionContextWindowContribution = class {
4961
+ unsubscribeNcpEvent = null;
4962
+ stopped = true;
4963
+ pendingTimers = /* @__PURE__ */ new Map();
4964
+ lastPublishedSignatureBySession = /* @__PURE__ */ new Map();
4965
+ constructor(kernel) {
4966
+ this.kernel = kernel;
4967
+ }
4968
+ start = () => {
4969
+ if (this.unsubscribeNcpEvent) return;
4970
+ this.stopped = false;
4971
+ this.unsubscribeNcpEvent = this.kernel.eventBus.on(eventKeys.ncpEvent, this.handleNcpEvent);
4839
4972
  };
4840
- removeSummaryIndex = async (sessionId) => {
4841
- (await this.loadSummaryIndex()).delete(sessionId);
4842
- await this.persistSummaryIndex();
4973
+ dispose = () => {
4974
+ this.unsubscribeNcpEvent?.();
4975
+ this.unsubscribeNcpEvent = null;
4976
+ this.stopped = true;
4977
+ for (const timer of this.pendingTimers.values()) clearTimeout(timer);
4978
+ this.pendingTimers.clear();
4979
+ this.lastPublishedSignatureBySession.clear();
4843
4980
  };
4844
- persistSummaryIndex = async () => {
4845
- const records = [...this.summaryIndex?.values() ?? []].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
4846
- await this.ensureJournalDir();
4847
- await writeFile(this.indexPath(), `${JSON.stringify({
4848
- version: 1,
4849
- records
4850
- })}\n`, "utf-8");
4981
+ handleNcpEvent = (event) => {
4982
+ const sessionId = readEventSessionId(event);
4983
+ if (!sessionId) return;
4984
+ if (event.type === NcpEventType.ContextWindowUpdated) {
4985
+ this.rememberPublishedContextWindow(sessionId, event.payload.contextWindow);
4986
+ return;
4987
+ }
4988
+ if (shouldRefreshImmediately(event)) {
4989
+ this.refreshNow(sessionId);
4990
+ return;
4991
+ }
4992
+ if (shouldRefreshDuringStream(event)) this.refreshSoon(sessionId);
4993
+ };
4994
+ refreshSoon = (sessionId) => {
4995
+ if (this.pendingTimers.has(sessionId)) return;
4996
+ const timer = setTimeout(() => {
4997
+ this.pendingTimers.delete(sessionId);
4998
+ this.refreshNow(sessionId);
4999
+ }, STREAM_REFRESH_DELAY_MS);
5000
+ this.pendingTimers.set(sessionId, timer);
5001
+ };
5002
+ refreshNow = (sessionId) => {
5003
+ const timer = this.pendingTimers.get(sessionId);
5004
+ if (timer) {
5005
+ clearTimeout(timer);
5006
+ this.pendingTimers.delete(sessionId);
5007
+ }
5008
+ this.publishContextWindow(sessionId).catch((error) => {
5009
+ console.error(`[session-context-window] failed to refresh ${sessionId}: ${formatBackgroundError$1(error)}`);
5010
+ });
4851
5011
  };
4852
- ensureJournalDir = async () => {
4853
- await mkdir(this.journalDir, { recursive: true });
5012
+ publishContextWindow = async (sessionId) => {
5013
+ if (this.stopped) return;
5014
+ const contextWindow = (await this.kernel.ncpSessionApi.getSession(sessionId))?.contextWindow;
5015
+ if (!isContextWindow(contextWindow) || this.stopped) return;
5016
+ const endpoint = this.kernel.agentRuntimeManager.currentHandle?.agentClientEndpoint;
5017
+ if (!endpoint) return;
5018
+ const signature = createSignature(contextWindow);
5019
+ if (this.lastPublishedSignatureBySession.get(sessionId) === signature) return;
5020
+ this.lastPublishedSignatureBySession.set(sessionId, signature);
5021
+ await endpoint.emit({
5022
+ type: NcpEventType.ContextWindowUpdated,
5023
+ payload: {
5024
+ sessionId,
5025
+ contextWindow
5026
+ }
5027
+ });
4854
5028
  };
4855
- sessionPath = (sessionId) => {
4856
- return join(this.journalDir, `${safeNcpSessionFilename(sessionId.replace(/:/g, "_"))}.jsonl`);
5029
+ rememberPublishedContextWindow = (sessionId, contextWindow) => {
5030
+ this.lastPublishedSignatureBySession.set(sessionId, createSignature(contextWindow));
4857
5031
  };
4858
- indexPath = () => resolve(this.journalDir, NCP_AGENT_SESSION_JOURNAL_INDEX_FILE);
4859
5032
  };
4860
5033
  //#endregion
4861
5034
  //#region src/contributions/session-activity-preview/utils/session-activity-preview-ncp-event.utils.ts
@@ -5159,7 +5332,7 @@ var NextclawKernel = class {
5159
5332
  sessionRequester: this.sessionRequests,
5160
5333
  resolveLearningLoopConfig: () => readLearningLoopRuntimeConfig(this.configManager.loadConfig())
5161
5334
  });
5162
- this.contributions = [new SessionActivityPreviewContribution(this)];
5335
+ this.contributions = [new SessionActivityPreviewContribution(this), new SessionContextWindowContribution(this)];
5163
5336
  }
5164
5337
  start = async () => {
5165
5338
  this.ncpSessionApi.start();
@@ -5187,4 +5360,4 @@ var NextclawKernel = class {
5187
5360
  };
5188
5361
  };
5189
5362
  //#endregion
5190
- export { AGENT_RUNTIME_SESSION_MESSAGE_INGRESS_TYPE, AgentManager, AgentRuntimeManager, AgentRuntimeRegistry, AutomationManager, BuiltinNarpRuntimeRegistrationService, ChannelManager, ConfigManager, ContextCompactionPreflightService, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_LEARNING_LOOP_TOOL_CALL_THRESHOLD, ExtensionManager, LEARNING_LOOP_DISABLED_METADATA_KEY, LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY, LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY, LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY, LEARNING_LOOP_REQUESTED_SKILLS, LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY, LearningLoopManager, LlmProviderManager, LlmUsageManager, LlmUsageStore, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NativeAgentRuntimeFactory, NcpSessionApiService, NextclawKernel, NextclawNcpContextBuilder, NextclawNcpToolRegistry, PluginRuntimeRegistrationController, ProviderManagerNcpLLMApi, SkillManager, ToolManager, UpdateManifestReader, buildLearningLoopTask, buildLlmUsageSummary, buildNcpUserMessage, buildUserMessageParts, createAgentRuntimeHandle, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createLlmUsageRecord, createNcpSessionSummary, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isReplyCapableChannel, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, readAccountIdForHints, readLearningLoopRuntimeConfig, resolveAgentHandoffDepth, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveLegacyEventType, runGatewayInboundLoop, runPromptOverNcp, sanitizeLlmUsage, serializeUnsignedUpdateManifest, streamPromptOverNcp, toNcpMessages, waitForAgentRuntimeSessionReply };
5363
+ export { AGENT_RUNTIME_SESSION_MESSAGE_INGRESS_TYPE, AgentManager, AgentRuntimeManager, AgentRuntimeRegistry, AutomationManager, BuiltinNarpRuntimeRegistrationService, ChannelManager, ConfigManager, ContextCompactionPreflightService, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_LEARNING_LOOP_TOOL_CALL_THRESHOLD, ExtensionManager, LEARNING_LOOP_DISABLED_METADATA_KEY, LEARNING_LOOP_LAST_REQUESTED_AT_METADATA_KEY, LEARNING_LOOP_LAST_REVIEW_SESSION_ID_METADATA_KEY, LEARNING_LOOP_LAST_TOOL_CALL_COUNT_METADATA_KEY, LEARNING_LOOP_REQUESTED_SKILLS, LEARNING_LOOP_SOURCE_SESSION_ID_METADATA_KEY, LearningLoopManager, LlmProviderManager, LlmUsageManager, LlmUsageStore, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NativeAgentRuntimeFactory, NcpSessionApiService, NextclawKernel, NextclawNcpContextBuilder, NextclawNcpToolRegistry, PluginRuntimeRegistrationController, ProviderManagerNcpLLMApi, SkillManager, ToolManager, UpdateManifestReader, buildLearningLoopTask, buildLlmUsageSummary, buildNcpUserMessage, buildUserMessageParts, createAgentRuntimeHandle, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createLlmUsageRecord, createNcpSessionSummary, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isReplyCapableChannel, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, readLearningLoopRuntimeConfig, resolveAgentHandoffDepth, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveLegacyEventType, runGatewayInboundLoop, runPromptOverNcp, sanitizeLlmUsage, serializeUnsignedUpdateManifest, streamPromptOverNcp, toNcpMessages, waitForAgentRuntimeSessionReply };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/kernel",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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/core": "0.12.17",
24
- "@nextclaw/mcp": "0.1.82",
25
- "@nextclaw/ncp-agent-runtime": "0.3.20",
26
- "@nextclaw/ncp-http-agent-server": "0.3.22",
27
- "@nextclaw/nextclaw-hermes-acp-bridge": "0.1.9",
28
- "@nextclaw/ncp-toolkit": "0.5.15",
29
- "@nextclaw/nextclaw-ncp-runtime-http-client": "0.1.9",
30
- "@nextclaw/openclaw-compat": "1.0.17",
31
- "@nextclaw/runtime": "0.2.49",
32
- "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.1.10",
33
- "@nextclaw/shared": "0.1.4",
34
- "@nextclaw/ncp": "0.5.10",
35
- "@nextclaw/ncp-mcp": "0.1.84"
23
+ "@nextclaw/core": "0.12.19",
24
+ "@nextclaw/mcp": "0.1.84",
25
+ "@nextclaw/ncp-agent-runtime": "0.3.22",
26
+ "@nextclaw/ncp-mcp": "0.1.86",
27
+ "@nextclaw/ncp-http-agent-server": "0.3.24",
28
+ "@nextclaw/ncp-toolkit": "0.5.17",
29
+ "@nextclaw/nextclaw-ncp-runtime-http-client": "0.1.11",
30
+ "@nextclaw/nextclaw-hermes-acp-bridge": "0.1.11",
31
+ "@nextclaw/shared": "0.1.6",
32
+ "@nextclaw/runtime": "0.2.51",
33
+ "@nextclaw/openclaw-compat": "1.0.19",
34
+ "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.1.12",
35
+ "@nextclaw/ncp": "0.5.12"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.17.6",