@nextclaw/kernel 0.1.5 → 0.1.7

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 +5 -1
  2. package/dist/index.js +441 -148
  3. package/package.json +14 -14
package/dist/index.d.ts CHANGED
@@ -494,6 +494,7 @@ declare class AgentRuntimeManager {
494
494
  private disposed;
495
495
  constructor(params: AgentRuntimeManagerOptions);
496
496
  get currentHandle(): AgentRuntimeHandle | null;
497
+ isLiveSessionRunning: (sessionId: string) => boolean;
497
498
  connectGatewayController: (gatewayController: GatewayController) => void;
498
499
  bootstrap: () => Promise<AgentRuntimeHandle>;
499
500
  private readonly materializeAgentSendEnvelope;
@@ -663,6 +664,7 @@ type NcpAgentSessionReadableStore = AgentSessionStore & {
663
664
  type NcpSessionApiServiceOptions = {
664
665
  eventBus: EventBus;
665
666
  getConfig: () => Config;
667
+ isLiveSessionRunning?: (sessionId: string) => boolean;
666
668
  ncpAgentSessionStore?: NcpAgentSessionReadableStore;
667
669
  sessionManager: SessionManager;
668
670
  };
@@ -679,6 +681,7 @@ declare class NcpSessionApiService implements NcpSessionApi {
679
681
  getSession: (sessionId: string) => Promise<NcpSessionSummary | null>;
680
682
  updateSession: (sessionId: string, patch: NcpSessionPatch) => Promise<NcpSessionSummary | null>;
681
683
  deleteSession: (sessionId: string) => Promise<void>;
684
+ private withLiveSessionStatus;
682
685
  }
683
686
  //#endregion
684
687
  //#region src/app/nextclaw-kernel.d.ts
@@ -722,7 +725,7 @@ declare class NextclawKernel {
722
725
  private readonly sessionLifecycleEvents;
723
726
  private readonly ncpAgentSessionStore;
724
727
  private readonly ncpAgentSessionJournalStore;
725
- private startPromise;
728
+ private readonly contributions;
726
729
  constructor(options?: NextclawKernelOptions);
727
730
  start: () => Promise<void>;
728
731
  dispose: () => Promise<void>;
@@ -885,6 +888,7 @@ declare class NextclawNcpToolRegistry implements NcpToolRegistry {
885
888
  private buildToolExecutionContext;
886
889
  private registerDefaultTools;
887
890
  private registerMessagingTools;
891
+ private resolveMessageChannels;
888
892
  private registerExtensionTools;
889
893
  private registerTool;
890
894
  private registerAdditionalTools;
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";
@@ -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$4(value) {
628
+ function isRecord$5(value) {
629
629
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
630
630
  }
631
631
  function cloneMetadata(value) {
632
- return isRecord$4(value) ? structuredClone(value) : void 0;
632
+ return isRecord$5(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$3(value) {
1879
+ function isRecord$4(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$3(args)) throw new Error("session_search requires an object argument.");
1929
+ if (!isRecord$4(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$3(value) {
1942
+ function readOptionalString$4(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$3(value);
1948
+ const base64 = readOptionalString$4(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$3(args.path);
2000
- const bytesBase64 = readOptionalString$3(args.bytesBase64);
2001
- const fileName = readOptionalString$3(args.fileName);
1999
+ const path = readOptionalString$4(args.path);
2000
+ const bytesBase64 = readOptionalString$4(args.bytesBase64);
2001
+ const fileName = readOptionalString$4(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$3(args?.path);
2009
- const fileName = readOptionalString$3(args?.fileName);
2010
- const mimeType = readOptionalString$3(args?.mimeType);
2008
+ const path = readOptionalString$4(args?.path);
2009
+ const fileName = readOptionalString$4(args?.fileName);
2010
+ const mimeType = readOptionalString$4(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$3(args?.assetUri);
2054
- const targetPath = readOptionalString$3(args?.targetPath);
2053
+ const assetUri = readOptionalString$4(args?.assetUri);
2054
+ const targetPath = readOptionalString$4(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$3(args?.assetUri);
2080
+ const assetUri = readOptionalString$4(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$2(params, key) {
2255
+ function readOptionalString$3(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$2(params, "notify")?.toLowerCase();
2316
+ const notifyMode = readOptionalString$3(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$2(params, "title"),
2325
+ title: readOptionalString$3(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$1(value) {
2337
+ function readOptionalString$2(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$1(value)?.toLowerCase();
2343
+ const normalized = readOptionalString$2(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$1(value.notify)?.toLowerCase();
2351
+ const notifyMode = readOptionalString$2(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$1(rawTitle),
2433
- agentId: readOptionalString$1(rawAgentId),
2434
- model: readOptionalString$1(rawModel),
2435
- runtime: readOptionalString$1(rawRuntime),
2432
+ title: readOptionalString$2(rawTitle),
2433
+ agentId: readOptionalString$2(rawAgentId),
2434
+ model: readOptionalString$2(rawModel),
2435
+ runtime: readOptionalString$2(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$1(rawTitle),
2443
+ title: readOptionalString$2(rawTitle),
2444
2444
  sourceSessionMetadata: this.sourceSessionMetadata,
2445
- agentId: readOptionalString$1(rawAgentId),
2446
- model: readOptionalString$1(rawModel),
2447
- runtime: readOptionalString$1(rawRuntime),
2445
+ agentId: readOptionalString$2(rawAgentId),
2446
+ model: readOptionalString$2(rawModel),
2447
+ runtime: readOptionalString$2(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$4(args)) return args;
2472
+ if (isRecord$5(args)) return args;
2473
2473
  if (typeof args === "string") try {
2474
2474
  const parsed = JSON.parse(args);
2475
- return isRecord$4(parsed) ? parsed : {};
2475
+ return isRecord$5(parsed) ? parsed : {};
2476
2476
  } catch {
2477
2477
  return {};
2478
2478
  }
@@ -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;
@@ -2664,14 +2668,14 @@ function readAccountIdForHints(metadata, sessionMetadata) {
2664
2668
  //#endregion
2665
2669
  //#region src/features/native-runtime/services/nextclaw-ncp-context-builder.service.ts
2666
2670
  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$2(value) {
2671
+ function isRecord$3(value) {
2668
2672
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2669
2673
  }
2670
2674
  function mergeInputMetadata(input) {
2671
- const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$2(message.metadata))?.metadata;
2675
+ const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$3(message.metadata))?.metadata;
2672
2676
  return {
2673
- ...isRecord$2(messageMetadata) ? structuredClone(messageMetadata) : {},
2674
- ...isRecord$2(input.metadata) ? structuredClone(input.metadata) : {}
2677
+ ...isRecord$3(messageMetadata) ? structuredClone(messageMetadata) : {},
2678
+ ...isRecord$3(input.metadata) ? structuredClone(input.metadata) : {}
2675
2679
  };
2676
2680
  }
2677
2681
  const REQUESTED_SKILLS_METADATA_READER = new RequestedSkillsMetadataReader();
@@ -2978,12 +2982,12 @@ function createContextWindowUpdatedEvent(params) {
2978
2982
  }
2979
2983
  };
2980
2984
  }
2981
- function isRecord$1(value) {
2985
+ function isRecord$2(value) {
2982
2986
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2983
2987
  }
2984
2988
  function resolveNativeReasoningNormalizationMode(params) {
2985
2989
  const runtimeEntry = params.config.agents.runtimes.entries.native?.config ?? params.config.ui.ncp.runtimes.native;
2986
- const runtimeMetadata = isRecord$1(runtimeEntry) ? runtimeEntry : {};
2990
+ const runtimeMetadata = isRecord$2(runtimeEntry) ? runtimeEntry : {};
2987
2991
  return readAssistantReasoningNormalizationModeFromMetadata(params.sessionMetadata) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalizationMode) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization_mode) ?? "think-tags";
2988
2992
  }
2989
2993
  function createSessionSearchTools(params) {
@@ -3240,7 +3244,7 @@ function createAgentRuntimeSessionRequestDispatcher(options) {
3240
3244
  }
3241
3245
  //#endregion
3242
3246
  //#region src/managers/agent-runtime.manager.ts
3243
- function readOptionalString(value) {
3247
+ function readOptionalString$1(value) {
3244
3248
  if (typeof value !== "string") return null;
3245
3249
  const trimmed = value.trim();
3246
3250
  return trimmed.length > 0 ? trimmed : null;
@@ -3251,7 +3255,7 @@ function readMessageTask(message) {
3251
3255
  }
3252
3256
  function readSessionMetadataString(metadata, ...keys) {
3253
3257
  for (const key of keys) {
3254
- const value = readOptionalString(metadata?.[key]);
3258
+ const value = readOptionalString$1(metadata?.[key]);
3255
3259
  if (value) return value;
3256
3260
  }
3257
3261
  }
@@ -3286,6 +3290,7 @@ var AgentRuntimeManager = class {
3286
3290
  get currentHandle() {
3287
3291
  return this.handle;
3288
3292
  }
3293
+ isLiveSessionRunning = (sessionId) => this.backend?.isLiveSessionRunning(sessionId) ?? false;
3289
3294
  connectGatewayController = (gatewayController) => {
3290
3295
  this.assertNotDisposed();
3291
3296
  this.gatewayController = gatewayController;
@@ -3350,7 +3355,7 @@ var AgentRuntimeManager = class {
3350
3355
  return this.handle;
3351
3356
  };
3352
3357
  materializeAgentSendEnvelope = (envelope) => {
3353
- const existingSessionId = readOptionalString(envelope.sessionId) ?? readOptionalString(envelope.message.sessionId);
3358
+ const existingSessionId = readOptionalString$1(envelope.sessionId) ?? readOptionalString$1(envelope.message.sessionId);
3354
3359
  if (existingSessionId) return {
3355
3360
  ...envelope,
3356
3361
  sessionId: existingSessionId,
@@ -3382,35 +3387,39 @@ var AgentRuntimeManager = class {
3382
3387
  }
3383
3388
  };
3384
3389
  };
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);
3390
+ createMaterializingAgentClientEndpoint = (backend) => {
3391
+ const send = async (envelope) => {
3392
+ const requestEnvelope = this.materializeAgentSendEnvelope(envelope);
3393
+ return await consumeNcpRunHandle(backend.send(requestEnvelope), createNcpRunHandle(requestEnvelope));
3394
+ };
3395
+ return {
3396
+ get manifest() {
3397
+ return backend.manifest;
3398
+ },
3399
+ start: backend.start,
3400
+ stop: backend.stop,
3401
+ subscribe: backend.subscribe,
3402
+ stream: async (payload) => {
3403
+ await consumeAgentEvents(backend.stream(payload));
3404
+ },
3405
+ abort: backend.abort,
3406
+ send,
3407
+ emit: async (event) => {
3408
+ switch (event.type) {
3409
+ case NcpEventType.MessageRequest:
3410
+ await send(event.payload);
3411
+ return;
3412
+ case NcpEventType.MessageStreamRequest:
3413
+ await consumeAgentEvents(backend.stream(event.payload));
3414
+ return;
3415
+ case NcpEventType.MessageAbort:
3416
+ await backend.abort(event.payload);
3417
+ return;
3418
+ default: await backend.emit(event);
3419
+ }
3411
3420
  }
3412
- }
3413
- });
3421
+ };
3422
+ };
3414
3423
  warmDerivedCapabilities = async () => {
3415
3424
  this.assertNotDisposed();
3416
3425
  this.warmupPromise ??= this.runDerivedCapabilityWarmup();
@@ -4182,13 +4191,6 @@ function buildUpdatedMetadata(params) {
4182
4191
  function normalizeSessionId(sessionId) {
4183
4192
  return sessionId.trim();
4184
4193
  }
4185
- function toSessionCandidate(record) {
4186
- const sessionId = normalizeSessionId(record.key);
4187
- return sessionId ? {
4188
- ...record,
4189
- sessionId
4190
- } : null;
4191
- }
4192
4194
  function createSessionListSummary(record) {
4193
4195
  return {
4194
4196
  sessionId: record.sessionId,
@@ -4201,9 +4203,6 @@ function createSessionListSummary(record) {
4201
4203
  metadata: structuredClone(record.metadata)
4202
4204
  };
4203
4205
  }
4204
- function readSessionActivityAt$1(record) {
4205
- return record.lastMessageAt ?? record.created_at;
4206
- }
4207
4206
  var NcpSessionApiService = class {
4208
4207
  unsubscribeSessionUpdated = null;
4209
4208
  contextWindowPreview;
@@ -4237,8 +4236,11 @@ var NcpSessionApiService = class {
4237
4236
  this.options.eventBus.emit(eventKeys.sessionSummaryDelete, { sessionKey: normalizedSessionKey });
4238
4237
  };
4239
4238
  listSessions = async (options) => {
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);
4239
+ if (this.options.ncpAgentSessionStore?.listSessionSummaries) return applyLimit(await this.options.ncpAgentSessionStore.listSessionSummaries(), options?.limit).map(this.withLiveSessionStatus);
4240
+ return applyLimit(this.options.sessionManager.listSessions().map((record) => ({
4241
+ ...record,
4242
+ sessionId: normalizeSessionId(record.key)
4243
+ })).filter((record) => Boolean(record)).sort((left, right) => (right.lastMessageAt ?? right.created_at).localeCompare(left.lastMessageAt ?? left.created_at)), options?.limit).map(createSessionListSummary).map(this.withLiveSessionStatus);
4242
4244
  };
4243
4245
  listSessionMessages = async (sessionId, options) => {
4244
4246
  const normalizedSessionId = normalizeSessionId(sessionId);
@@ -4251,7 +4253,7 @@ var NcpSessionApiService = class {
4251
4253
  const normalizedSessionId = normalizeSessionId(sessionId);
4252
4254
  if (normalizedSessionId && this.options.ncpAgentSessionStore?.getSession) {
4253
4255
  const record = await this.options.ncpAgentSessionStore.getSession(normalizedSessionId);
4254
- if (record) return createNcpSessionSummary({
4256
+ if (record) return this.withLiveSessionStatus(createNcpSessionSummary({
4255
4257
  sessionId: normalizedSessionId,
4256
4258
  agentId: record.agentId,
4257
4259
  messages: record.messages,
@@ -4265,12 +4267,12 @@ var NcpSessionApiService = class {
4265
4267
  sessionId: normalizedSessionId,
4266
4268
  sessionMessages: record.messages
4267
4269
  })
4268
- });
4270
+ }));
4269
4271
  }
4270
4272
  const session = normalizedSessionId ? this.options.sessionManager.getIfExists(normalizedSessionId) : null;
4271
4273
  if (!session) return null;
4272
4274
  const messages = toNcpMessages(normalizedSessionId, session.messages);
4273
- return createNcpSessionSummary({
4275
+ return this.withLiveSessionStatus(createNcpSessionSummary({
4274
4276
  sessionId: normalizedSessionId,
4275
4277
  agentId: session.agentId,
4276
4278
  messages,
@@ -4284,7 +4286,7 @@ var NcpSessionApiService = class {
4284
4286
  sessionId: normalizedSessionId,
4285
4287
  sessionMessages: messages
4286
4288
  })
4287
- });
4289
+ }));
4288
4290
  };
4289
4291
  updateSession = async (sessionId, patch) => {
4290
4292
  const normalizedSessionId = normalizeSessionId(sessionId);
@@ -4321,6 +4323,10 @@ var NcpSessionApiService = class {
4321
4323
  this.options.sessionManager.delete(normalizedSessionId);
4322
4324
  await this.publishSessionChange(normalizedSessionId);
4323
4325
  };
4326
+ withLiveSessionStatus = (summary) => this.options.isLiveSessionRunning?.(summary.sessionId) ? {
4327
+ ...summary,
4328
+ status: "running"
4329
+ } : summary;
4324
4330
  };
4325
4331
  //#endregion
4326
4332
  //#region src/utils/ncp-session-metadata.utils.ts
@@ -4351,21 +4357,6 @@ var NcpAgentLegacySessionStore = class {
4351
4357
  metadata: structuredClone(session.metadata)
4352
4358
  };
4353
4359
  };
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
- };
4368
- };
4369
4360
  listSessionMessages = (sessionId) => this.getSession(sessionId)?.messages ?? [];
4370
4361
  listSessionSummaries = () => this.sessionManager.listSessions().map((record) => ({
4371
4362
  sessionId: record.key,
@@ -4431,7 +4422,6 @@ var NcpAgentSessionStoreAdapter = class {
4431
4422
  };
4432
4423
  }
4433
4424
  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
4425
  listSessionMessages = async (sessionId) => {
4436
4426
  const journalStore = this.options.journalStore;
4437
4427
  return journalStore && await journalStore.hasSession(sessionId) ? await journalStore.listSessionMessages(sessionId) : this.legacyStore.listSessionMessages(sessionId);
@@ -4505,7 +4495,7 @@ function normalizeNcpAgentId(agentId) {
4505
4495
  const normalized = agentId?.trim().toLowerCase();
4506
4496
  return normalized ? normalized : void 0;
4507
4497
  }
4508
- function isRecord(value) {
4498
+ function isRecord$1(value) {
4509
4499
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4510
4500
  }
4511
4501
  function toIsoString(value, fallback) {
@@ -4563,10 +4553,19 @@ function upsertNcpAgentSessionSummaryEvent(params) {
4563
4553
  }
4564
4554
  async function replayNcpAgentSessionEvents(events) {
4565
4555
  const stateManager = new DefaultNcpAgentConversationStateManager();
4566
- for (const event of events) await stateManager.dispatch(event.type === NcpEventType.MessageCompleted ? createCompletedMessageEvent(event) : structuredClone(event));
4556
+ for (const event of events) await stateManager.dispatch(createReplayEvent(event));
4567
4557
  const snapshot = stateManager.getSnapshot();
4568
4558
  return [...snapshot.messages.map((message) => structuredClone(message)), ...snapshot.streamingMessage ? [structuredClone(snapshot.streamingMessage)] : []];
4569
4559
  }
4560
+ function createReplayEvent(event) {
4561
+ const replayEvent = structuredClone(event);
4562
+ if (replayEvent.type === NcpEventType.MessageCompleted) return {
4563
+ type: NcpEventType.MessageSent,
4564
+ payload: replayEvent.payload
4565
+ };
4566
+ if (replayEvent.type === NcpEventType.MessageSent && replayEvent.payload.message.role === "assistant" && (replayEvent.payload.message.status === "pending" || replayEvent.payload.message.status === "streaming")) replayEvent.payload.message.status = "final";
4567
+ return replayEvent;
4568
+ }
4570
4569
  function readNcpSessionSummaryActivityAt(summary) {
4571
4570
  return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
4572
4571
  }
@@ -4604,19 +4603,14 @@ function readSummaryLabelFromEvent(event) {
4604
4603
  if (message?.role !== "user") return null;
4605
4604
  return resolveAutoSessionLabel([message]);
4606
4605
  }
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
4606
  //#endregion
4619
4607
  //#region src/stores/ncp-agent-session-journal.store.ts
4608
+ function serializeJournalEntry(entry) {
4609
+ const serialized = JSON.stringify(entry);
4610
+ if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
4611
+ if (!isRecord$1(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
4612
+ return serialized;
4613
+ }
4620
4614
  var NcpAgentSessionJournalStore = class {
4621
4615
  sessions = /* @__PURE__ */ new Map();
4622
4616
  nextSeqBySession = /* @__PURE__ */ new Map();
@@ -4648,14 +4642,6 @@ var NcpAgentSessionJournalStore = class {
4648
4642
  this.sessions.set(normalizedSessionId, loaded);
4649
4643
  return structuredClone(loaded.record);
4650
4644
  };
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
4645
  listSessionSummaries = async () => {
4660
4646
  return [...(await this.loadSummaryIndex()).values()].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
4661
4647
  };
@@ -4684,7 +4670,7 @@ var NcpAgentSessionJournalStore = class {
4684
4670
  }
4685
4671
  }
4686
4672
  }))];
4687
- await writeFile(this.sessionPath(sessionId), `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
4673
+ await writeFile(this.sessionPath(sessionId), `${entries.map(serializeJournalEntry).join("\n")}\n`, "utf-8");
4688
4674
  const nextSeq = nextRecord.messages.length + 1;
4689
4675
  this.sessions.set(sessionId, {
4690
4676
  record: nextRecord,
@@ -4718,7 +4704,7 @@ var NcpAgentSessionJournalStore = class {
4718
4704
  const hasJournal = Boolean(existing) || this.nextSeqBySession.has(sessionId);
4719
4705
  const nextSeq = this.nextSeqBySession.get(sessionId) ?? existing?.nextSeq ?? 1;
4720
4706
  const path = this.sessionPath(sessionId);
4721
- if (!hasJournal) await appendFile(path, `${JSON.stringify(createNcpAgentSessionJournalMetadataEntry(session))}\n`, "utf-8");
4707
+ if (!hasJournal) await this.appendJournalEntry(path, createNcpAgentSessionJournalMetadataEntry(session));
4722
4708
  const entry = {
4723
4709
  _type: "event",
4724
4710
  version: 1,
@@ -4726,7 +4712,7 @@ var NcpAgentSessionJournalStore = class {
4726
4712
  timestamp: updatedAt,
4727
4713
  event: structuredClone(event)
4728
4714
  };
4729
- await appendFile(path, `${JSON.stringify(entry)}\n`, "utf-8");
4715
+ await this.appendJournalEntry(path, entry);
4730
4716
  this.nextSeqBySession.set(sessionId, nextSeq + 1);
4731
4717
  this.sessions.delete(sessionId);
4732
4718
  await this.upsertSummaryIndexForEvent({
@@ -4765,18 +4751,24 @@ var NcpAgentSessionJournalStore = class {
4765
4751
  let updatedAt = createdAt;
4766
4752
  let nextSeq = 1;
4767
4753
  const events = [];
4768
- for (const line of raw.split("\n")) {
4754
+ for (const [index, line] of raw.split("\n").entries()) {
4769
4755
  if (!line.trim()) continue;
4770
- const parsed = JSON.parse(line);
4771
- if (!isRecord(parsed)) continue;
4756
+ let parsed;
4757
+ try {
4758
+ parsed = JSON.parse(line);
4759
+ } catch (error) {
4760
+ console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
4761
+ continue;
4762
+ }
4763
+ if (!isRecord$1(parsed)) continue;
4772
4764
  if (parsed._type === "metadata") {
4773
- metadata = isRecord(parsed.metadata) ? structuredClone(parsed.metadata) : {};
4765
+ metadata = isRecord$1(parsed.metadata) ? structuredClone(parsed.metadata) : {};
4774
4766
  agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
4775
4767
  createdAt = toIsoString(parsed.created_at, createdAt);
4776
4768
  updatedAt = toIsoString(parsed.updated_at, updatedAt);
4777
4769
  continue;
4778
4770
  }
4779
- if (parsed._type === "event" && isRecord(parsed.event)) {
4771
+ if (parsed._type === "event" && isRecord$1(parsed.event)) {
4780
4772
  const seq = Number(parsed.seq);
4781
4773
  nextSeq = Math.max(nextSeq, Number.isFinite(seq) ? Math.trunc(seq) + 1 : nextSeq);
4782
4774
  updatedAt = toIsoString(parsed.timestamp, updatedAt);
@@ -4792,6 +4784,9 @@ var NcpAgentSessionJournalStore = class {
4792
4784
  events
4793
4785
  };
4794
4786
  };
4787
+ appendJournalEntry = async (path, entry) => {
4788
+ await appendFile(path, `${serializeJournalEntry(entry)}\n`, "utf-8");
4789
+ };
4795
4790
  loadSummaryIndex = async () => {
4796
4791
  if (this.summaryIndex) return this.summaryIndex;
4797
4792
  try {
@@ -4860,6 +4855,306 @@ var NcpAgentSessionJournalStore = class {
4860
4855
  indexPath = () => resolve(this.journalDir, NCP_AGENT_SESSION_JOURNAL_INDEX_FILE);
4861
4856
  };
4862
4857
  //#endregion
4858
+ //#region src/contributions/session-context-window/index.ts
4859
+ const STREAM_REFRESH_DELAY_MS = 1500;
4860
+ function formatBackgroundError$1(error) {
4861
+ if (error instanceof Error) return error.stack ?? error.message;
4862
+ return String(error);
4863
+ }
4864
+ function readEventSessionId(event) {
4865
+ const payload = "payload" in event ? event.payload : null;
4866
+ if (!payload || typeof payload !== "object") return null;
4867
+ return "sessionId" in payload && typeof payload.sessionId === "string" ? payload.sessionId.trim() || null : null;
4868
+ }
4869
+ function isContextWindow(value) {
4870
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4871
+ }
4872
+ function createSignature(value) {
4873
+ return JSON.stringify(value);
4874
+ }
4875
+ function shouldRefreshDuringStream(event) {
4876
+ switch (event.type) {
4877
+ case NcpEventType.MessageTextDelta:
4878
+ case NcpEventType.MessageReasoningDelta:
4879
+ case NcpEventType.MessageToolCallArgsDelta:
4880
+ case NcpEventType.MessageToolCallResult: return true;
4881
+ default: return false;
4882
+ }
4883
+ }
4884
+ function shouldRefreshImmediately(event) {
4885
+ switch (event.type) {
4886
+ case NcpEventType.MessageCompleted:
4887
+ case NcpEventType.MessageFailed:
4888
+ case NcpEventType.MessageAbort:
4889
+ case NcpEventType.RunFinished:
4890
+ case NcpEventType.RunError: return true;
4891
+ default: return false;
4892
+ }
4893
+ }
4894
+ var SessionContextWindowContribution = class {
4895
+ unsubscribeNcpEvent = null;
4896
+ stopped = true;
4897
+ pendingTimers = /* @__PURE__ */ new Map();
4898
+ lastPublishedSignatureBySession = /* @__PURE__ */ new Map();
4899
+ constructor(kernel) {
4900
+ this.kernel = kernel;
4901
+ }
4902
+ start = () => {
4903
+ if (this.unsubscribeNcpEvent) return;
4904
+ this.stopped = false;
4905
+ this.unsubscribeNcpEvent = this.kernel.eventBus.on(eventKeys.ncpEvent, this.handleNcpEvent);
4906
+ };
4907
+ dispose = () => {
4908
+ this.unsubscribeNcpEvent?.();
4909
+ this.unsubscribeNcpEvent = null;
4910
+ this.stopped = true;
4911
+ for (const timer of this.pendingTimers.values()) clearTimeout(timer);
4912
+ this.pendingTimers.clear();
4913
+ this.lastPublishedSignatureBySession.clear();
4914
+ };
4915
+ handleNcpEvent = (event) => {
4916
+ const sessionId = readEventSessionId(event);
4917
+ if (!sessionId) return;
4918
+ if (event.type === NcpEventType.ContextWindowUpdated) {
4919
+ this.rememberPublishedContextWindow(sessionId, event.payload.contextWindow);
4920
+ return;
4921
+ }
4922
+ if (shouldRefreshImmediately(event)) {
4923
+ this.refreshNow(sessionId);
4924
+ return;
4925
+ }
4926
+ if (shouldRefreshDuringStream(event)) this.refreshSoon(sessionId);
4927
+ };
4928
+ refreshSoon = (sessionId) => {
4929
+ if (this.pendingTimers.has(sessionId)) return;
4930
+ const timer = setTimeout(() => {
4931
+ this.pendingTimers.delete(sessionId);
4932
+ this.refreshNow(sessionId);
4933
+ }, STREAM_REFRESH_DELAY_MS);
4934
+ this.pendingTimers.set(sessionId, timer);
4935
+ };
4936
+ refreshNow = (sessionId) => {
4937
+ const timer = this.pendingTimers.get(sessionId);
4938
+ if (timer) {
4939
+ clearTimeout(timer);
4940
+ this.pendingTimers.delete(sessionId);
4941
+ }
4942
+ this.publishContextWindow(sessionId).catch((error) => {
4943
+ console.error(`[session-context-window] failed to refresh ${sessionId}: ${formatBackgroundError$1(error)}`);
4944
+ });
4945
+ };
4946
+ publishContextWindow = async (sessionId) => {
4947
+ if (this.stopped) return;
4948
+ const contextWindow = (await this.kernel.ncpSessionApi.getSession(sessionId))?.contextWindow;
4949
+ if (!isContextWindow(contextWindow) || this.stopped) return;
4950
+ const endpoint = this.kernel.agentRuntimeManager.currentHandle?.agentClientEndpoint;
4951
+ if (!endpoint) return;
4952
+ const signature = createSignature(contextWindow);
4953
+ if (this.lastPublishedSignatureBySession.get(sessionId) === signature) return;
4954
+ this.lastPublishedSignatureBySession.set(sessionId, signature);
4955
+ await endpoint.emit({
4956
+ type: NcpEventType.ContextWindowUpdated,
4957
+ payload: {
4958
+ sessionId,
4959
+ contextWindow
4960
+ }
4961
+ });
4962
+ };
4963
+ rememberPublishedContextWindow = (sessionId, contextWindow) => {
4964
+ this.lastPublishedSignatureBySession.set(sessionId, createSignature(contextWindow));
4965
+ };
4966
+ };
4967
+ //#endregion
4968
+ //#region src/contributions/session-activity-preview/utils/session-activity-preview-ncp-event.utils.ts
4969
+ const PREVIEW_TEXT_MAX_LENGTH = 160;
4970
+ function readSessionId(value) {
4971
+ if (typeof value !== "string") return null;
4972
+ const trimmed = value.trim();
4973
+ return trimmed.length > 0 ? trimmed : null;
4974
+ }
4975
+ function compactPreviewText(value) {
4976
+ return value.replace(/\s+/g, " ").trim();
4977
+ }
4978
+ function truncatePreviewText(value) {
4979
+ const compacted = compactPreviewText(value);
4980
+ if (compacted.length <= PREVIEW_TEXT_MAX_LENGTH) return compacted;
4981
+ return compacted.slice(0, PREVIEW_TEXT_MAX_LENGTH - 1).trimEnd();
4982
+ }
4983
+ function readMessagePreviewText(message) {
4984
+ const chunks = [];
4985
+ for (const part of message.parts) if ((part.type === "text" || part.type === "rich-text") && part.text.trim()) chunks.push(part.text);
4986
+ const previewText = truncatePreviewText(chunks.join(" "));
4987
+ return previewText.length > 0 ? previewText : null;
4988
+ }
4989
+ function createProjection(sessionId, preview) {
4990
+ if (!sessionId) return null;
4991
+ return {
4992
+ sessionId,
4993
+ preview
4994
+ };
4995
+ }
4996
+ function formatErrorStatus(error) {
4997
+ if (typeof error === "string" && error.trim()) return `运行出错:${truncatePreviewText(error)}`;
4998
+ if (error && typeof error === "object" && "message" in error) {
4999
+ const message = error.message;
5000
+ if (typeof message === "string" && message.trim()) return `运行出错:${truncatePreviewText(message)}`;
5001
+ }
5002
+ return "运行出错";
5003
+ }
5004
+ function createSessionActivityPreviewFromNcpEvent(event, timestamp) {
5005
+ switch (event.type) {
5006
+ case NcpEventType.RunStarted: return createProjection(readSessionId(event.payload.sessionId), {
5007
+ state: "running",
5008
+ statusText: "正在处理...",
5009
+ timestamp
5010
+ });
5011
+ case NcpEventType.RunFinished: return createProjection(readSessionId(event.payload.sessionId), {
5012
+ state: "completed",
5013
+ timestamp
5014
+ });
5015
+ case NcpEventType.RunError: return createProjection(readSessionId(event.payload.sessionId), {
5016
+ state: "failed",
5017
+ statusText: formatErrorStatus(event.payload.error),
5018
+ timestamp
5019
+ });
5020
+ case NcpEventType.MessageSent: {
5021
+ const text = readMessagePreviewText(event.payload.message);
5022
+ if (!text || event.payload.message.role !== "user") return null;
5023
+ return createProjection(readSessionId(event.payload.sessionId), {
5024
+ state: "running",
5025
+ statusText: text,
5026
+ timestamp: event.payload.message.timestamp || timestamp
5027
+ });
5028
+ }
5029
+ case NcpEventType.MessageCompleted: {
5030
+ const text = readMessagePreviewText(event.payload.message);
5031
+ if (!text || event.payload.message.role !== "assistant") return null;
5032
+ return createProjection(readSessionId(event.payload.sessionId), {
5033
+ state: "completed",
5034
+ replyText: text,
5035
+ timestamp: event.payload.message.timestamp || timestamp
5036
+ });
5037
+ }
5038
+ case NcpEventType.MessageFailed: return createProjection(readSessionId(event.payload.sessionId), {
5039
+ state: "failed",
5040
+ statusText: formatErrorStatus(event.payload.error),
5041
+ timestamp
5042
+ });
5043
+ case NcpEventType.MessageToolCallStart: return createProjection(readSessionId(event.payload.sessionId), {
5044
+ state: "running",
5045
+ statusText: `正在调用工具:${event.payload.toolName}`,
5046
+ timestamp
5047
+ });
5048
+ case NcpEventType.MessageToolCallEnd:
5049
+ case NcpEventType.MessageToolCallResult: return createProjection(readSessionId(event.payload.sessionId), {
5050
+ state: "running",
5051
+ statusText: "工具调用完成",
5052
+ timestamp
5053
+ });
5054
+ default: return null;
5055
+ }
5056
+ }
5057
+ //#endregion
5058
+ //#region src/contributions/session-activity-preview/utils/session-activity-preview-metadata.utils.ts
5059
+ const SESSION_ACTIVITY_PREVIEW_METADATA_KEY = "last_activity_preview";
5060
+ const SESSION_ACTIVITY_PREVIEW_STATES = new Set([
5061
+ "running",
5062
+ "completed",
5063
+ "failed",
5064
+ "idle"
5065
+ ]);
5066
+ function isRecord(value) {
5067
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5068
+ }
5069
+ function readOptionalString(value) {
5070
+ if (typeof value !== "string") return;
5071
+ const trimmed = value.trim();
5072
+ return trimmed.length > 0 ? trimmed : void 0;
5073
+ }
5074
+ function readSessionActivityPreviewMetadata(value) {
5075
+ if (!isRecord(value)) return null;
5076
+ const state = value.state;
5077
+ const timestamp = readOptionalString(value.timestamp);
5078
+ if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
5079
+ return {
5080
+ state,
5081
+ timestamp,
5082
+ ...readOptionalString(value.statusText) ? { statusText: readOptionalString(value.statusText) } : {},
5083
+ ...readOptionalString(value.replyText) ? { replyText: readOptionalString(value.replyText) } : {}
5084
+ };
5085
+ }
5086
+ function compareIsoTimestamp(left, right) {
5087
+ const leftTime = Date.parse(left);
5088
+ const rightTime = Date.parse(right);
5089
+ if (!Number.isFinite(leftTime) || !Number.isFinite(rightTime)) return left.localeCompare(right);
5090
+ return leftTime - rightTime;
5091
+ }
5092
+ function mergeSessionActivityPreview(current, incoming) {
5093
+ if (current && compareIsoTimestamp(incoming.timestamp, current.timestamp) < 0) return current;
5094
+ return {
5095
+ state: incoming.state,
5096
+ timestamp: incoming.timestamp,
5097
+ ...incoming.statusText ?? (incoming.state === "completed" ? current?.statusText : void 0) ? { statusText: incoming.statusText ?? current?.statusText } : {},
5098
+ ...incoming.replyText ?? current?.replyText ? { replyText: incoming.replyText ?? current?.replyText } : {}
5099
+ };
5100
+ }
5101
+ function areSessionActivityPreviewsEqual(left, right) {
5102
+ return Boolean(left) && left?.state === right.state && left.timestamp === right.timestamp && left.statusText === right.statusText && left.replyText === right.replyText;
5103
+ }
5104
+ function writeSessionActivityPreviewMetadata(metadata, projection) {
5105
+ const current = readSessionActivityPreviewMetadata(metadata?.[SESSION_ACTIVITY_PREVIEW_METADATA_KEY]);
5106
+ const next = mergeSessionActivityPreview(current, projection.preview);
5107
+ if (areSessionActivityPreviewsEqual(current, next)) return null;
5108
+ return {
5109
+ ...metadata ?? {},
5110
+ [SESSION_ACTIVITY_PREVIEW_METADATA_KEY]: next
5111
+ };
5112
+ }
5113
+ //#endregion
5114
+ //#region src/contributions/session-activity-preview/index.ts
5115
+ function formatBackgroundError(error) {
5116
+ if (error instanceof Error) return error.stack ?? error.message;
5117
+ return String(error);
5118
+ }
5119
+ function readMetadata(value) {
5120
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
5121
+ return value;
5122
+ }
5123
+ var SessionActivityPreviewContribution = class {
5124
+ unsubscribeNcpEvent = null;
5125
+ stopped = true;
5126
+ constructor(kernel) {
5127
+ this.kernel = kernel;
5128
+ }
5129
+ start = () => {
5130
+ if (this.unsubscribeNcpEvent) return;
5131
+ this.stopped = false;
5132
+ this.unsubscribeNcpEvent = this.kernel.eventBus.on(eventKeys.ncpEvent, (event) => {
5133
+ const projection = createSessionActivityPreviewFromNcpEvent(event, (/* @__PURE__ */ new Date()).toISOString());
5134
+ if (!projection) return;
5135
+ this.updatePreview(projection);
5136
+ });
5137
+ };
5138
+ dispose = () => {
5139
+ this.unsubscribeNcpEvent?.();
5140
+ this.unsubscribeNcpEvent = null;
5141
+ this.stopped = true;
5142
+ };
5143
+ updatePreview = (projection) => {
5144
+ this.persistPreview(projection).catch((error) => {
5145
+ console.error(`[session-activity-preview] failed to update ${projection.sessionId}: ${formatBackgroundError(error)}`);
5146
+ });
5147
+ };
5148
+ persistPreview = async (projection) => {
5149
+ if (this.stopped) return;
5150
+ const summary = await this.kernel.ncpSessionApi.getSession(projection.sessionId);
5151
+ if (!summary || this.stopped) return;
5152
+ const nextMetadata = writeSessionActivityPreviewMetadata(readMetadata(summary.metadata), projection);
5153
+ if (!nextMetadata) return;
5154
+ await this.kernel.ncpSessionApi.updateSession(projection.sessionId, { metadata: nextMetadata });
5155
+ };
5156
+ };
5157
+ //#endregion
4863
5158
  //#region src/app/nextclaw-kernel.ts
4864
5159
  function resolveKernelSessionsDir(options) {
4865
5160
  const homeDir = options.homeDir?.trim();
@@ -4904,7 +5199,7 @@ var NextclawKernel = class {
4904
5199
  sessionLifecycleEvents;
4905
5200
  ncpAgentSessionStore;
4906
5201
  ncpAgentSessionJournalStore;
4907
- startPromise = null;
5202
+ contributions;
4908
5203
  constructor(options = {}) {
4909
5204
  const sessionsDir = resolveKernelSessionsDir(options);
4910
5205
  this.sessions = new SessionManager({ sessionsDir });
@@ -4942,12 +5237,6 @@ var NextclawKernel = class {
4942
5237
  channels: this.channels,
4943
5238
  providerManager: this.llmProviders
4944
5239
  });
4945
- this.ncpSessionApi = new NcpSessionApiService({
4946
- eventBus: this.eventBus,
4947
- getConfig: this.configManager.loadConfig,
4948
- ncpAgentSessionStore: this.ncpAgentSessionStore,
4949
- sessionManager: this.sessions
4950
- });
4951
5240
  this.agentRuntimeManager = new AgentRuntimeManager({
4952
5241
  bus: this.messageBus,
4953
5242
  providerManager: this.llmProviders,
@@ -4964,30 +5253,34 @@ var NextclawKernel = class {
4964
5253
  llmUsage: this.llmUsage,
4965
5254
  onSessionUpdated: this.publishSessionUpdated
4966
5255
  });
5256
+ this.ncpSessionApi = new NcpSessionApiService({
5257
+ eventBus: this.eventBus,
5258
+ getConfig: this.configManager.loadConfig,
5259
+ isLiveSessionRunning: this.agentRuntimeManager.isLiveSessionRunning,
5260
+ ncpAgentSessionStore: this.ncpAgentSessionStore,
5261
+ sessionManager: this.sessions
5262
+ });
4967
5263
  this.learningLoop = new LearningLoopManager({
4968
5264
  eventBus: this.eventBus,
4969
5265
  sessionManager: this.sessions,
4970
5266
  sessionRequester: this.sessionRequests,
4971
5267
  resolveLearningLoopConfig: () => readLearningLoopRuntimeConfig(this.configManager.loadConfig())
4972
5268
  });
5269
+ this.contributions = [new SessionActivityPreviewContribution(this), new SessionContextWindowContribution(this)];
4973
5270
  }
4974
5271
  start = async () => {
4975
- this.startPromise ??= Promise.resolve().then(() => {
4976
- this.ncpSessionApi.start();
4977
- }).then(() => this.sessionSearch.start()).then(() => this.agentRuntimeManager.bootstrap()).then(() => {
4978
- this.learningLoop.start();
4979
- }).catch((error) => {
4980
- this.startPromise = null;
4981
- throw error;
4982
- });
4983
- return this.startPromise;
5272
+ this.ncpSessionApi.start();
5273
+ this.sessionSearch.start();
5274
+ for (const contribution of this.contributions) contribution.start();
5275
+ this.agentRuntimeManager.bootstrap();
5276
+ this.learningLoop.start();
4984
5277
  };
4985
5278
  dispose = async () => {
4986
5279
  this.learningLoop.dispose();
5280
+ for (const contribution of this.contributions) contribution.dispose();
4987
5281
  this.ncpSessionApi.dispose();
4988
5282
  await this.agentRuntimeManager.dispose();
4989
5283
  await this.sessionSearch.dispose();
4990
- this.startPromise = null;
4991
5284
  };
4992
5285
  publishSessionUpdated = (sessionKey) => {
4993
5286
  this.sessionLifecycleEvents.publishSessionUpdated(sessionKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/kernel",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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.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"
23
+ "@nextclaw/core": "0.12.18",
24
+ "@nextclaw/ncp-http-agent-server": "0.3.23",
25
+ "@nextclaw/mcp": "0.1.83",
26
+ "@nextclaw/ncp-agent-runtime": "0.3.21",
27
+ "@nextclaw/ncp-mcp": "0.1.85",
28
+ "@nextclaw/ncp-toolkit": "0.5.16",
29
+ "@nextclaw/nextclaw-hermes-acp-bridge": "0.1.10",
30
+ "@nextclaw/nextclaw-ncp-runtime-stdio-client": "0.1.11",
31
+ "@nextclaw/ncp": "0.5.11",
32
+ "@nextclaw/openclaw-compat": "1.0.18",
33
+ "@nextclaw/runtime": "0.2.50",
34
+ "@nextclaw/shared": "0.1.5",
35
+ "@nextclaw/nextclaw-ncp-runtime-http-client": "0.1.10"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.17.6",