@nextclaw/kernel 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +14 -1
- package/dist/index.js +881 -112
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import { AgentRouteResolver, CONTEXT_COMPACTION_METADATA_KEY, ChannelManager, Ch
|
|
|
3
3
|
import { EventBus, Ingress, createAppEventKey, createTypedKey, eventKeys } from "@nextclaw/shared";
|
|
4
4
|
import { DefaultNcpAgentRuntime, LocalAssetStore, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool } from "@nextclaw/ncp-agent-runtime";
|
|
5
5
|
import { NcpEventType, readAssistantReasoningNormalizationMode, readAssistantReasoningNormalizationModeFromMetadata, sanitizeAssistantReplyTags, writeAssistantReasoningNormalizationModeToMetadata } from "@nextclaw/ncp";
|
|
6
|
-
import { DefaultNcpAgentBackend, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
|
|
6
|
+
import { DefaultNcpAgentBackend, DefaultNcpAgentConversationStateManager, createAgentClientFromServer } from "@nextclaw/ncp-toolkit";
|
|
7
7
|
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
8
|
-
import { access, readFile } from "node:fs/promises";
|
|
8
|
+
import { access, appendFile, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
9
9
|
import { appendFileSync, constants, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { BUILTIN_PROVIDER_PLUGINS } from "@nextclaw/runtime";
|
|
11
11
|
import { HttpRuntimeConfigResolver, HttpRuntimeNcpAgentRuntime } from "@nextclaw/nextclaw-ncp-runtime-http-client";
|
|
@@ -592,10 +592,10 @@ var AgentRuntimeRegistry = class {
|
|
|
592
592
|
//#endregion
|
|
593
593
|
//#region src/features/ncp-dispatch/utils/agent-runtime-handle.utils.ts
|
|
594
594
|
function createAgentRuntimeHandle(params) {
|
|
595
|
-
const { backend, runtimeRegistry, refreshPluginRuntimeRegistrations, refreshConfiguredRuntimeEntries, applyMcpConfig, dispose, assetStore } = params;
|
|
595
|
+
const { agentClientEndpoint, backend, runtimeRegistry, refreshPluginRuntimeRegistrations, refreshConfiguredRuntimeEntries, applyMcpConfig, dispose, assetStore } = params;
|
|
596
596
|
return {
|
|
597
597
|
basePath: "/api/ncp/agent",
|
|
598
|
-
agentClientEndpoint: createAgentClientFromServer(backend),
|
|
598
|
+
agentClientEndpoint: agentClientEndpoint ?? createAgentClientFromServer(backend),
|
|
599
599
|
streamProvider: backend,
|
|
600
600
|
runApi: backend,
|
|
601
601
|
sessionApi: backend,
|
|
@@ -625,11 +625,11 @@ function normalizeString(value) {
|
|
|
625
625
|
const trimmed = value.trim();
|
|
626
626
|
return trimmed.length > 0 ? trimmed : null;
|
|
627
627
|
}
|
|
628
|
-
function isRecord$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2000
|
-
const bytesBase64 = readOptionalString$
|
|
2001
|
-
const fileName = readOptionalString$
|
|
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$
|
|
2009
|
-
const fileName = readOptionalString$
|
|
2010
|
-
const mimeType = readOptionalString$
|
|
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$
|
|
2054
|
-
const targetPath = readOptionalString$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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(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(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(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(rawTitle),
|
|
2433
|
-
agentId: readOptionalString(rawAgentId),
|
|
2434
|
-
model: readOptionalString(rawModel),
|
|
2435
|
-
runtime: readOptionalString(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(rawTitle),
|
|
2443
|
+
title: readOptionalString$2(rawTitle),
|
|
2444
2444
|
sourceSessionMetadata: this.sourceSessionMetadata,
|
|
2445
|
-
agentId: readOptionalString(rawAgentId),
|
|
2446
|
-
model: readOptionalString(rawModel),
|
|
2447
|
-
runtime: readOptionalString(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$
|
|
2472
|
+
if (isRecord$5(args)) return args;
|
|
2473
2473
|
if (typeof args === "string") try {
|
|
2474
2474
|
const parsed = JSON.parse(args);
|
|
2475
|
-
return isRecord$
|
|
2475
|
+
return isRecord$5(parsed) ? parsed : {};
|
|
2476
2476
|
} catch {
|
|
2477
2477
|
return {};
|
|
2478
2478
|
}
|
|
@@ -2664,14 +2664,14 @@ function readAccountIdForHints(metadata, sessionMetadata) {
|
|
|
2664
2664
|
//#endregion
|
|
2665
2665
|
//#region src/features/native-runtime/services/nextclaw-ncp-context-builder.service.ts
|
|
2666
2666
|
const TIME_HINT_TRIGGER_PATTERNS = [/\b(now|right now|current time|what time|today|tonight|tomorrow|yesterday|this morning|this afternoon|this evening|date)\b/i, /(现在|此刻|当前时间|现在几点|几点了|今天|今晚|今早|今晨|明天|昨天|日期)/];
|
|
2667
|
-
function isRecord$
|
|
2667
|
+
function isRecord$3(value) {
|
|
2668
2668
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2669
2669
|
}
|
|
2670
2670
|
function mergeInputMetadata(input) {
|
|
2671
|
-
const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$
|
|
2671
|
+
const messageMetadata = input.messages.slice().reverse().find((message) => isRecord$3(message.metadata))?.metadata;
|
|
2672
2672
|
return {
|
|
2673
|
-
...isRecord$
|
|
2674
|
-
...isRecord$
|
|
2673
|
+
...isRecord$3(messageMetadata) ? structuredClone(messageMetadata) : {},
|
|
2674
|
+
...isRecord$3(input.metadata) ? structuredClone(input.metadata) : {}
|
|
2675
2675
|
};
|
|
2676
2676
|
}
|
|
2677
2677
|
const REQUESTED_SKILLS_METADATA_READER = new RequestedSkillsMetadataReader();
|
|
@@ -2978,12 +2978,12 @@ function createContextWindowUpdatedEvent(params) {
|
|
|
2978
2978
|
}
|
|
2979
2979
|
};
|
|
2980
2980
|
}
|
|
2981
|
-
function isRecord(value) {
|
|
2981
|
+
function isRecord$2(value) {
|
|
2982
2982
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2983
2983
|
}
|
|
2984
2984
|
function resolveNativeReasoningNormalizationMode(params) {
|
|
2985
2985
|
const runtimeEntry = params.config.agents.runtimes.entries.native?.config ?? params.config.ui.ncp.runtimes.native;
|
|
2986
|
-
const runtimeMetadata = isRecord(runtimeEntry) ? runtimeEntry : {};
|
|
2986
|
+
const runtimeMetadata = isRecord$2(runtimeEntry) ? runtimeEntry : {};
|
|
2987
2987
|
return readAssistantReasoningNormalizationModeFromMetadata(params.sessionMetadata) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoningNormalizationMode) ?? readAssistantReasoningNormalizationMode(runtimeMetadata.reasoning_normalization_mode) ?? "think-tags";
|
|
2988
2988
|
}
|
|
2989
2989
|
function createSessionSearchTools(params) {
|
|
@@ -3240,6 +3240,24 @@ function createAgentRuntimeSessionRequestDispatcher(options) {
|
|
|
3240
3240
|
}
|
|
3241
3241
|
//#endregion
|
|
3242
3242
|
//#region src/managers/agent-runtime.manager.ts
|
|
3243
|
+
function readOptionalString$1(value) {
|
|
3244
|
+
if (typeof value !== "string") return null;
|
|
3245
|
+
const trimmed = value.trim();
|
|
3246
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
3247
|
+
}
|
|
3248
|
+
function readMessageTask(message) {
|
|
3249
|
+
for (const part of message.parts) if ((part.type === "text" || part.type === "rich-text" || part.type === "reasoning") && part.text.trim()) return part.text.trim();
|
|
3250
|
+
return "Session";
|
|
3251
|
+
}
|
|
3252
|
+
function readSessionMetadataString(metadata, ...keys) {
|
|
3253
|
+
for (const key of keys) {
|
|
3254
|
+
const value = readOptionalString$1(metadata?.[key]);
|
|
3255
|
+
if (value) return value;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
async function consumeAgentEvents(events) {
|
|
3259
|
+
for await (const event of events);
|
|
3260
|
+
}
|
|
3243
3261
|
var AgentRuntimeManager = class {
|
|
3244
3262
|
runtimeRegistry = new AgentRuntimeRegistry();
|
|
3245
3263
|
mcpRuntimeSupport;
|
|
@@ -3268,6 +3286,7 @@ var AgentRuntimeManager = class {
|
|
|
3268
3286
|
get currentHandle() {
|
|
3269
3287
|
return this.handle;
|
|
3270
3288
|
}
|
|
3289
|
+
isLiveSessionRunning = (sessionId) => this.backend?.isLiveSessionRunning(sessionId) ?? false;
|
|
3271
3290
|
connectGatewayController = (gatewayController) => {
|
|
3272
3291
|
this.assertNotDisposed();
|
|
3273
3292
|
this.gatewayController = gatewayController;
|
|
@@ -3321,6 +3340,7 @@ var AgentRuntimeManager = class {
|
|
|
3321
3340
|
await this.backend.start();
|
|
3322
3341
|
this.handle = createAgentRuntimeHandle({
|
|
3323
3342
|
backend: this.backend,
|
|
3343
|
+
agentClientEndpoint: this.createMaterializingAgentClientEndpoint(this.backend),
|
|
3324
3344
|
runtimeRegistry: this.runtimeRegistry,
|
|
3325
3345
|
refreshPluginRuntimeRegistrations: this.pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations,
|
|
3326
3346
|
refreshConfiguredRuntimeEntries: this.refreshConfiguredRuntimeEntries,
|
|
@@ -3330,6 +3350,68 @@ var AgentRuntimeManager = class {
|
|
|
3330
3350
|
});
|
|
3331
3351
|
return this.handle;
|
|
3332
3352
|
};
|
|
3353
|
+
materializeAgentSendEnvelope = (envelope) => {
|
|
3354
|
+
const existingSessionId = readOptionalString$1(envelope.sessionId) ?? readOptionalString$1(envelope.message.sessionId);
|
|
3355
|
+
if (existingSessionId) return {
|
|
3356
|
+
...envelope,
|
|
3357
|
+
sessionId: existingSessionId,
|
|
3358
|
+
message: {
|
|
3359
|
+
...envelope.message,
|
|
3360
|
+
sessionId: existingSessionId
|
|
3361
|
+
}
|
|
3362
|
+
};
|
|
3363
|
+
const metadata = envelope.metadata ?? {};
|
|
3364
|
+
const createdSession = this.params.sessions.createSession({
|
|
3365
|
+
task: readMessageTask(envelope.message),
|
|
3366
|
+
title: readSessionMetadataString(metadata, "label", "title"),
|
|
3367
|
+
sourceSessionMetadata: {},
|
|
3368
|
+
metadataOverrides: metadata,
|
|
3369
|
+
agentId: readSessionMetadataString(metadata, "agent_id", "agentId"),
|
|
3370
|
+
model: readSessionMetadataString(metadata, "preferred_model", "model"),
|
|
3371
|
+
runtime: readSessionMetadataString(metadata, "runtime", "session_type"),
|
|
3372
|
+
sessionType: readSessionMetadataString(metadata, "session_type", "runtime"),
|
|
3373
|
+
thinkingLevel: readSessionMetadataString(metadata, "preferred_thinking", "thinking"),
|
|
3374
|
+
projectRoot: readSessionMetadataString(metadata, "project_root")
|
|
3375
|
+
});
|
|
3376
|
+
this.params.onSessionUpdated(createdSession.sessionId);
|
|
3377
|
+
return {
|
|
3378
|
+
...envelope,
|
|
3379
|
+
sessionId: createdSession.sessionId,
|
|
3380
|
+
message: {
|
|
3381
|
+
...envelope.message,
|
|
3382
|
+
sessionId: createdSession.sessionId
|
|
3383
|
+
}
|
|
3384
|
+
};
|
|
3385
|
+
};
|
|
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);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
});
|
|
3333
3415
|
warmDerivedCapabilities = async () => {
|
|
3334
3416
|
this.assertNotDisposed();
|
|
3335
3417
|
this.warmupPromise ??= this.runDerivedCapabilityWarmup();
|
|
@@ -4069,11 +4151,12 @@ var NcpLifecycleEventBridge = class {
|
|
|
4069
4151
|
//#endregion
|
|
4070
4152
|
//#region src/utils/ncp-session-summary.utils.ts
|
|
4071
4153
|
function createNcpSessionSummary(params) {
|
|
4072
|
-
const { sessionId, agentId, messages, updatedAt, status, metadata, contextWindow } = params;
|
|
4154
|
+
const { sessionId, agentId, messages, createdAt, updatedAt, status, metadata, contextWindow } = params;
|
|
4073
4155
|
return {
|
|
4074
4156
|
sessionId,
|
|
4075
4157
|
...agentId ? { agentId } : {},
|
|
4076
4158
|
messageCount: messages.length,
|
|
4159
|
+
createdAt,
|
|
4077
4160
|
updatedAt,
|
|
4078
4161
|
...messages.length > 0 ? { lastMessageAt: messages[messages.length - 1]?.timestamp ?? updatedAt } : {},
|
|
4079
4162
|
status,
|
|
@@ -4100,6 +4183,18 @@ function buildUpdatedMetadata(params) {
|
|
|
4100
4183
|
function normalizeSessionId(sessionId) {
|
|
4101
4184
|
return sessionId.trim();
|
|
4102
4185
|
}
|
|
4186
|
+
function createSessionListSummary(record) {
|
|
4187
|
+
return {
|
|
4188
|
+
sessionId: record.sessionId,
|
|
4189
|
+
...record.agentId ? { agentId: record.agentId } : {},
|
|
4190
|
+
messageCount: record.messageCount ?? 0,
|
|
4191
|
+
createdAt: record.created_at,
|
|
4192
|
+
updatedAt: record.updated_at,
|
|
4193
|
+
...record.lastMessageAt ? { lastMessageAt: record.lastMessageAt } : {},
|
|
4194
|
+
status: "idle",
|
|
4195
|
+
metadata: structuredClone(record.metadata)
|
|
4196
|
+
};
|
|
4197
|
+
}
|
|
4103
4198
|
var NcpSessionApiService = class {
|
|
4104
4199
|
unsubscribeSessionUpdated = null;
|
|
4105
4200
|
contextWindowPreview;
|
|
@@ -4133,39 +4228,47 @@ var NcpSessionApiService = class {
|
|
|
4133
4228
|
this.options.eventBus.emit(eventKeys.sessionSummaryDelete, { sessionKey: normalizedSessionKey });
|
|
4134
4229
|
};
|
|
4135
4230
|
listSessions = async (options) => {
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
if (!session) continue;
|
|
4142
|
-
summaries.push(createNcpSessionSummary({
|
|
4143
|
-
sessionId,
|
|
4144
|
-
agentId: session.agentId,
|
|
4145
|
-
messages: toNcpMessages(sessionId, session.messages),
|
|
4146
|
-
updatedAt: session.updatedAt.toISOString(),
|
|
4147
|
-
status: "idle",
|
|
4148
|
-
metadata: session.metadata
|
|
4149
|
-
}));
|
|
4150
|
-
}
|
|
4151
|
-
summaries.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
4152
|
-
return applyLimit(summaries, options?.limit);
|
|
4231
|
+
if (this.options.ncpAgentSessionStore?.listSessionSummaries) return applyLimit(await this.options.ncpAgentSessionStore.listSessionSummaries(), options?.limit).map(this.withLiveSessionStatus);
|
|
4232
|
+
return applyLimit(this.options.sessionManager.listSessions().map((record) => ({
|
|
4233
|
+
...record,
|
|
4234
|
+
sessionId: normalizeSessionId(record.key)
|
|
4235
|
+
})).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);
|
|
4153
4236
|
};
|
|
4154
4237
|
listSessionMessages = async (sessionId, options) => {
|
|
4155
4238
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
4239
|
+
if (normalizedSessionId && this.options.ncpAgentSessionStore?.listSessionMessages) return applyLimit(await this.options.ncpAgentSessionStore.listSessionMessages(normalizedSessionId), options?.limit);
|
|
4156
4240
|
const session = normalizedSessionId ? this.options.sessionManager.getIfExists(normalizedSessionId) : null;
|
|
4157
4241
|
if (!session) return [];
|
|
4158
4242
|
return applyLimit(toNcpMessages(normalizedSessionId, session.messages).map((message) => structuredClone(message)), options?.limit);
|
|
4159
4243
|
};
|
|
4160
4244
|
getSession = async (sessionId) => {
|
|
4161
4245
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
4246
|
+
if (normalizedSessionId && this.options.ncpAgentSessionStore?.getSession) {
|
|
4247
|
+
const record = await this.options.ncpAgentSessionStore.getSession(normalizedSessionId);
|
|
4248
|
+
if (record) return this.withLiveSessionStatus(createNcpSessionSummary({
|
|
4249
|
+
sessionId: normalizedSessionId,
|
|
4250
|
+
agentId: record.agentId,
|
|
4251
|
+
messages: record.messages,
|
|
4252
|
+
createdAt: record.createdAt ?? record.updatedAt,
|
|
4253
|
+
updatedAt: record.updatedAt,
|
|
4254
|
+
status: "idle",
|
|
4255
|
+
metadata: record.metadata,
|
|
4256
|
+
contextWindow: this.contextWindowPreview.preview({
|
|
4257
|
+
contextWindowOwner: "nextclaw",
|
|
4258
|
+
requestMetadata: record.metadata ?? {},
|
|
4259
|
+
sessionId: normalizedSessionId,
|
|
4260
|
+
sessionMessages: record.messages
|
|
4261
|
+
})
|
|
4262
|
+
}));
|
|
4263
|
+
}
|
|
4162
4264
|
const session = normalizedSessionId ? this.options.sessionManager.getIfExists(normalizedSessionId) : null;
|
|
4163
4265
|
if (!session) return null;
|
|
4164
4266
|
const messages = toNcpMessages(normalizedSessionId, session.messages);
|
|
4165
|
-
return createNcpSessionSummary({
|
|
4267
|
+
return this.withLiveSessionStatus(createNcpSessionSummary({
|
|
4166
4268
|
sessionId: normalizedSessionId,
|
|
4167
4269
|
agentId: session.agentId,
|
|
4168
4270
|
messages,
|
|
4271
|
+
createdAt: session.createdAt.toISOString(),
|
|
4169
4272
|
updatedAt: session.updatedAt.toISOString(),
|
|
4170
4273
|
status: "idle",
|
|
4171
4274
|
metadata: session.metadata,
|
|
@@ -4175,10 +4278,25 @@ var NcpSessionApiService = class {
|
|
|
4175
4278
|
sessionId: normalizedSessionId,
|
|
4176
4279
|
sessionMessages: messages
|
|
4177
4280
|
})
|
|
4178
|
-
});
|
|
4281
|
+
}));
|
|
4179
4282
|
};
|
|
4180
4283
|
updateSession = async (sessionId, patch) => {
|
|
4181
4284
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
4285
|
+
if (normalizedSessionId && this.options.ncpAgentSessionStore) {
|
|
4286
|
+
const existing = await this.options.ncpAgentSessionStore.getSession(normalizedSessionId);
|
|
4287
|
+
if (existing) {
|
|
4288
|
+
await this.options.ncpAgentSessionStore.replaceSession({
|
|
4289
|
+
...existing,
|
|
4290
|
+
metadata: buildUpdatedMetadata({
|
|
4291
|
+
existingMetadata: existing.metadata,
|
|
4292
|
+
patch
|
|
4293
|
+
}),
|
|
4294
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4295
|
+
});
|
|
4296
|
+
await this.publishSessionChange(normalizedSessionId);
|
|
4297
|
+
return await this.getSession(normalizedSessionId);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4182
4300
|
const session = normalizedSessionId ? this.options.sessionManager.getIfExists(normalizedSessionId) : null;
|
|
4183
4301
|
if (!session) return null;
|
|
4184
4302
|
session.metadata = buildUpdatedMetadata({
|
|
@@ -4193,9 +4311,14 @@ var NcpSessionApiService = class {
|
|
|
4193
4311
|
deleteSession = async (sessionId) => {
|
|
4194
4312
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
4195
4313
|
if (!normalizedSessionId) return;
|
|
4314
|
+
if (this.options.ncpAgentSessionStore) await this.options.ncpAgentSessionStore.deleteSession(normalizedSessionId);
|
|
4196
4315
|
this.options.sessionManager.delete(normalizedSessionId);
|
|
4197
4316
|
await this.publishSessionChange(normalizedSessionId);
|
|
4198
4317
|
};
|
|
4318
|
+
withLiveSessionStatus = (summary) => this.options.isLiveSessionRunning?.(summary.sessionId) ? {
|
|
4319
|
+
...summary,
|
|
4320
|
+
status: "running"
|
|
4321
|
+
} : summary;
|
|
4199
4322
|
};
|
|
4200
4323
|
//#endregion
|
|
4201
4324
|
//#region src/utils/ncp-session-metadata.utils.ts
|
|
@@ -4205,58 +4328,72 @@ function resolvePersistedSessionMetadata(params) {
|
|
|
4205
4328
|
return mergeSessionMetadata(preserveExistingMetadata ? mergeSessionMetadata(currentMetadata, messageMetadata) : mergeSessionMetadata({}, messageMetadata), cloneMetadata(sessionRecord.metadata));
|
|
4206
4329
|
}
|
|
4207
4330
|
//#endregion
|
|
4208
|
-
//#region src/
|
|
4209
|
-
function readAgentIdFromMetadata(metadata) {
|
|
4210
|
-
return normalizeString(metadata?.agent_id)?.toLowerCase() ?? normalizeString(metadata?.agentId)?.toLowerCase() ?? void 0;
|
|
4211
|
-
}
|
|
4331
|
+
//#region src/stores/ncp-agent-legacy-session.store.ts
|
|
4212
4332
|
function resolveSessionRecordAgentId(record) {
|
|
4213
|
-
return normalizeString(record.agentId)?.toLowerCase() ??
|
|
4333
|
+
return normalizeString(record.agentId)?.toLowerCase() ?? normalizeString(record.metadata?.agent_id)?.toLowerCase() ?? normalizeString(record.metadata?.agentId)?.toLowerCase();
|
|
4214
4334
|
}
|
|
4215
|
-
var
|
|
4335
|
+
var NcpAgentLegacySessionStore = class {
|
|
4216
4336
|
constructor(sessionManager, options = {}) {
|
|
4217
4337
|
this.sessionManager = sessionManager;
|
|
4218
4338
|
this.options = options;
|
|
4219
4339
|
}
|
|
4220
|
-
getSession =
|
|
4340
|
+
getSession = (sessionId) => {
|
|
4221
4341
|
const session = this.sessionManager.getIfExists(sessionId);
|
|
4222
4342
|
if (!session) return null;
|
|
4223
4343
|
return {
|
|
4224
4344
|
sessionId,
|
|
4225
4345
|
...session.agentId ? { agentId: session.agentId } : {},
|
|
4226
4346
|
messages: toNcpMessages(sessionId, session.messages),
|
|
4347
|
+
createdAt: session.createdAt.toISOString(),
|
|
4227
4348
|
updatedAt: session.updatedAt.toISOString(),
|
|
4228
4349
|
metadata: structuredClone(session.metadata)
|
|
4229
4350
|
};
|
|
4230
4351
|
};
|
|
4231
|
-
|
|
4232
|
-
const
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
});
|
|
4246
|
-
}
|
|
4247
|
-
sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
4248
|
-
return sessions;
|
|
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
|
+
};
|
|
4249
4366
|
};
|
|
4250
|
-
|
|
4251
|
-
|
|
4367
|
+
listSessionMessages = (sessionId) => this.getSession(sessionId)?.messages ?? [];
|
|
4368
|
+
listSessionSummaries = () => this.sessionManager.listSessions().map((record) => ({
|
|
4369
|
+
sessionId: record.key,
|
|
4370
|
+
...record.agentId ? { agentId: record.agentId } : {},
|
|
4371
|
+
messageCount: record.messageCount ?? 0,
|
|
4372
|
+
createdAt: record.created_at,
|
|
4373
|
+
updatedAt: record.updated_at,
|
|
4374
|
+
...record.lastMessageAt ? { lastMessageAt: record.lastMessageAt } : {},
|
|
4375
|
+
status: "idle",
|
|
4376
|
+
metadata: structuredClone(record.metadata)
|
|
4377
|
+
}));
|
|
4378
|
+
saveSession = (sessionRecord) => this.persistSession(sessionRecord, true);
|
|
4379
|
+
replaceSession = (sessionRecord) => this.persistSession(sessionRecord, false);
|
|
4380
|
+
deleteSession = (sessionId) => {
|
|
4381
|
+
const existing = this.getSession(sessionId);
|
|
4382
|
+
if (!existing) return null;
|
|
4383
|
+
this.sessionManager.delete(sessionId);
|
|
4384
|
+
this.options.onSessionUpdated?.(sessionId);
|
|
4385
|
+
return existing;
|
|
4386
|
+
};
|
|
4387
|
+
persistSession = async (sessionRecord, preserveExistingMetadata) => {
|
|
4252
4388
|
const session = this.sessionManager.getIfExists(sessionRecord.sessionId) ?? this.sessionManager.getOrCreate(sessionRecord.sessionId);
|
|
4253
4389
|
const legacyMessages = toLegacyMessages(sessionRecord.messages);
|
|
4254
4390
|
const nextAgentId = resolveSessionRecordAgentId(sessionRecord);
|
|
4255
4391
|
if (nextAgentId) session.agentId = nextAgentId;
|
|
4392
|
+
if (sessionRecord.createdAt) session.createdAt = new Date(ensureIsoTimestamp(sessionRecord.createdAt, session.createdAt.toISOString()));
|
|
4256
4393
|
session.metadata = resolvePersistedSessionMetadata({
|
|
4257
4394
|
currentMetadata: session.metadata,
|
|
4258
4395
|
sessionRecord,
|
|
4259
|
-
preserveExistingMetadata
|
|
4396
|
+
preserveExistingMetadata
|
|
4260
4397
|
});
|
|
4261
4398
|
this.sessionManager.clear(session);
|
|
4262
4399
|
for (const message of legacyMessages) this.sessionManager.appendEvent(session, {
|
|
@@ -4268,19 +4405,647 @@ var NcpAgentSessionStoreAdapter = class {
|
|
|
4268
4405
|
this.sessionManager.save(session);
|
|
4269
4406
|
this.options.onSessionUpdated?.(sessionRecord.sessionId);
|
|
4270
4407
|
};
|
|
4408
|
+
};
|
|
4409
|
+
//#endregion
|
|
4410
|
+
//#region src/services/ncp-agent-session-store-adapter.service.ts
|
|
4411
|
+
function readSessionActivityAt(record) {
|
|
4412
|
+
return record.messages.at(-1)?.timestamp ?? record.createdAt ?? record.updatedAt;
|
|
4413
|
+
}
|
|
4414
|
+
var NcpAgentSessionStoreAdapter = class {
|
|
4415
|
+
appendSessionEvent;
|
|
4416
|
+
legacyStore;
|
|
4417
|
+
constructor(sessionManager, options = {}) {
|
|
4418
|
+
this.options = options;
|
|
4419
|
+
this.legacyStore = new NcpAgentLegacySessionStore(sessionManager, { onSessionUpdated: options.onSessionUpdated });
|
|
4420
|
+
const journalStore = options.journalStore;
|
|
4421
|
+
if (journalStore) this.appendSessionEvent = async (params) => {
|
|
4422
|
+
const { event, session } = params;
|
|
4423
|
+
if (!await journalStore.hasSession(session.sessionId)) {
|
|
4424
|
+
const legacySession = this.legacyStore.getSession(session.sessionId);
|
|
4425
|
+
if (legacySession) await journalStore.replaceSession(legacySession);
|
|
4426
|
+
}
|
|
4427
|
+
await journalStore.appendSessionEvent(params);
|
|
4428
|
+
if (isSessionSummaryRefreshEvent(event)) this.options.onSessionUpdated?.(session.sessionId);
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4431
|
+
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
|
+
listSessionMessages = async (sessionId) => {
|
|
4434
|
+
const journalStore = this.options.journalStore;
|
|
4435
|
+
return journalStore && await journalStore.hasSession(sessionId) ? await journalStore.listSessionMessages(sessionId) : this.legacyStore.listSessionMessages(sessionId);
|
|
4436
|
+
};
|
|
4437
|
+
listSessionSummaries = async () => {
|
|
4438
|
+
const journalSummaries = await this.options.journalStore?.listSessionSummaries() ?? [];
|
|
4439
|
+
const journalIds = new Set(journalSummaries.map((summary) => summary.sessionId));
|
|
4440
|
+
const legacySummaries = this.legacyStore.listSessionSummaries().filter((summary) => !journalIds.has(summary.sessionId));
|
|
4441
|
+
return [...journalSummaries, ...legacySummaries].sort((left, right) => readSummaryActivityAt(right).localeCompare(readSummaryActivityAt(left)));
|
|
4442
|
+
};
|
|
4443
|
+
listSessions = async () => {
|
|
4444
|
+
const summaries = await this.listSessionSummaries();
|
|
4445
|
+
const sessions = [];
|
|
4446
|
+
for (const summary of summaries) {
|
|
4447
|
+
const sessionId = summary.sessionId.trim();
|
|
4448
|
+
if (!sessionId) continue;
|
|
4449
|
+
const session = await this.getSession(sessionId);
|
|
4450
|
+
if (!session) continue;
|
|
4451
|
+
sessions.push(session);
|
|
4452
|
+
}
|
|
4453
|
+
sessions.sort((left, right) => readSessionActivityAt(right).localeCompare(readSessionActivityAt(left)));
|
|
4454
|
+
return sessions;
|
|
4455
|
+
};
|
|
4271
4456
|
saveSession = async (sessionRecord) => {
|
|
4272
|
-
|
|
4457
|
+
const journalStore = this.options.journalStore;
|
|
4458
|
+
if (journalStore && await journalStore.hasSession(sessionRecord.sessionId)) {
|
|
4459
|
+
await journalStore.replaceSession(sessionRecord);
|
|
4460
|
+
this.options.onSessionUpdated?.(sessionRecord.sessionId);
|
|
4461
|
+
return;
|
|
4462
|
+
}
|
|
4463
|
+
await this.legacyStore.saveSession(sessionRecord);
|
|
4273
4464
|
};
|
|
4274
4465
|
replaceSession = async (sessionRecord) => {
|
|
4275
|
-
|
|
4466
|
+
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;
|
|
4471
|
+
}
|
|
4472
|
+
await this.legacyStore.replaceSession(sessionRecord);
|
|
4276
4473
|
};
|
|
4277
4474
|
deleteSession = async (sessionId) => {
|
|
4278
|
-
const
|
|
4279
|
-
|
|
4280
|
-
this.
|
|
4281
|
-
|
|
4475
|
+
const journalSession = await this.options.journalStore?.deleteSession(sessionId);
|
|
4476
|
+
const legacySession = this.legacyStore.deleteSession(sessionId);
|
|
4477
|
+
if (journalSession && !legacySession) this.options.onSessionUpdated?.(sessionId);
|
|
4478
|
+
return journalSession ?? legacySession;
|
|
4479
|
+
};
|
|
4480
|
+
};
|
|
4481
|
+
function readSummaryActivityAt(summary) {
|
|
4482
|
+
return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
|
|
4483
|
+
}
|
|
4484
|
+
function isSessionSummaryRefreshEvent(event) {
|
|
4485
|
+
switch (event.type) {
|
|
4486
|
+
case NcpEventType.MessageSent:
|
|
4487
|
+
case NcpEventType.MessageCompleted:
|
|
4488
|
+
case NcpEventType.MessageAbort:
|
|
4489
|
+
case NcpEventType.RunFinished:
|
|
4490
|
+
case NcpEventType.RunError: return true;
|
|
4491
|
+
default: return false;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
const NCP_AGENT_SESSION_JOURNAL_INDEX_FILE = ".ncp-agent-session-index.json";
|
|
4495
|
+
const AUTO_SESSION_LABEL_MAX_LENGTH = 64;
|
|
4496
|
+
function normalizeNcpSessionId(sessionId) {
|
|
4497
|
+
return sessionId.trim();
|
|
4498
|
+
}
|
|
4499
|
+
function safeNcpSessionFilename(value) {
|
|
4500
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
4501
|
+
}
|
|
4502
|
+
function normalizeNcpAgentId(agentId) {
|
|
4503
|
+
const normalized = agentId?.trim().toLowerCase();
|
|
4504
|
+
return normalized ? normalized : void 0;
|
|
4505
|
+
}
|
|
4506
|
+
function isRecord$1(value) {
|
|
4507
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4508
|
+
}
|
|
4509
|
+
function toIsoString(value, fallback) {
|
|
4510
|
+
if (typeof value !== "string") return fallback;
|
|
4511
|
+
const parsed = Date.parse(value);
|
|
4512
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : fallback;
|
|
4513
|
+
}
|
|
4514
|
+
function createNcpAgentSessionSummary(record) {
|
|
4515
|
+
const metadata = structuredClone(record.metadata ?? {});
|
|
4516
|
+
const label = readOptionalText(metadata.label) ?? resolveAutoSessionLabel(record.messages);
|
|
4517
|
+
if (label) metadata.label = label;
|
|
4518
|
+
const lastMessageAt = readMessageTimestamp(record.messages.at(-1));
|
|
4519
|
+
return {
|
|
4520
|
+
sessionId: record.sessionId,
|
|
4521
|
+
...normalizeNcpAgentId(record.agentId) ? { agentId: normalizeNcpAgentId(record.agentId) } : {},
|
|
4522
|
+
messageCount: record.messages.length,
|
|
4523
|
+
...record.createdAt ? { createdAt: record.createdAt } : {},
|
|
4524
|
+
updatedAt: record.updatedAt,
|
|
4525
|
+
...lastMessageAt ? { lastMessageAt } : {},
|
|
4526
|
+
status: "idle",
|
|
4527
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4530
|
+
function createNcpAgentSessionJournalMetadataEntry(record) {
|
|
4531
|
+
return {
|
|
4532
|
+
_type: "metadata",
|
|
4533
|
+
version: 1,
|
|
4534
|
+
created_at: record.createdAt ?? record.updatedAt,
|
|
4535
|
+
updated_at: record.updatedAt,
|
|
4536
|
+
...normalizeNcpAgentId(record.agentId) ? { agent_id: normalizeNcpAgentId(record.agentId) } : {},
|
|
4537
|
+
metadata: structuredClone(record.metadata ?? {})
|
|
4538
|
+
};
|
|
4539
|
+
}
|
|
4540
|
+
function upsertNcpAgentSessionSummaryEvent(params) {
|
|
4541
|
+
const { current, event, session, updatedAt } = params;
|
|
4542
|
+
const metadata = {
|
|
4543
|
+
...current?.metadata ? structuredClone(current.metadata) : {},
|
|
4544
|
+
...session.metadata ? structuredClone(session.metadata) : {}
|
|
4545
|
+
};
|
|
4546
|
+
const label = readOptionalText(metadata.label) ?? readSummaryLabelFromEvent(event);
|
|
4547
|
+
if (label) metadata.label = truncateLabel(label);
|
|
4548
|
+
const eventMessage = readMessageFromSummaryEvent(event);
|
|
4549
|
+
const messageCount = current ? current.messageCount + (eventMessage ? 1 : 0) : eventMessage ? 1 : 0;
|
|
4550
|
+
const lastMessageAt = readMessageTimestamp(eventMessage) ?? current?.lastMessageAt;
|
|
4551
|
+
return {
|
|
4552
|
+
sessionId: session.sessionId,
|
|
4553
|
+
...normalizeNcpAgentId(session.agentId ?? current?.agentId) ? { agentId: normalizeNcpAgentId(session.agentId ?? current?.agentId) } : {},
|
|
4554
|
+
messageCount,
|
|
4555
|
+
createdAt: current?.createdAt ?? session.createdAt ?? updatedAt,
|
|
4556
|
+
updatedAt,
|
|
4557
|
+
...lastMessageAt ? { lastMessageAt } : {},
|
|
4558
|
+
status: "idle",
|
|
4559
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4562
|
+
async function replayNcpAgentSessionEvents(events) {
|
|
4563
|
+
const stateManager = new DefaultNcpAgentConversationStateManager();
|
|
4564
|
+
for (const event of events) await stateManager.dispatch(event.type === NcpEventType.MessageCompleted ? createCompletedMessageEvent(event) : structuredClone(event));
|
|
4565
|
+
const snapshot = stateManager.getSnapshot();
|
|
4566
|
+
return [...snapshot.messages.map((message) => structuredClone(message)), ...snapshot.streamingMessage ? [structuredClone(snapshot.streamingMessage)] : []];
|
|
4567
|
+
}
|
|
4568
|
+
function readNcpSessionSummaryActivityAt(summary) {
|
|
4569
|
+
return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
|
|
4570
|
+
}
|
|
4571
|
+
function readMessageTimestamp(message) {
|
|
4572
|
+
return typeof message?.timestamp === "string" && Number.isFinite(Date.parse(message.timestamp)) ? new Date(message.timestamp).toISOString() : void 0;
|
|
4573
|
+
}
|
|
4574
|
+
function truncateLabel(value) {
|
|
4575
|
+
const chars = Array.from(value);
|
|
4576
|
+
return chars.length <= AUTO_SESSION_LABEL_MAX_LENGTH ? value : `${chars.slice(0, AUTO_SESSION_LABEL_MAX_LENGTH).join("")}...`;
|
|
4577
|
+
}
|
|
4578
|
+
function readOptionalText(value) {
|
|
4579
|
+
if (typeof value !== "string") return null;
|
|
4580
|
+
const trimmed = value.trim();
|
|
4581
|
+
return trimmed ? trimmed : null;
|
|
4582
|
+
}
|
|
4583
|
+
function resolveAutoSessionLabel(messages) {
|
|
4584
|
+
for (const message of messages) {
|
|
4585
|
+
if (message.role !== "user") continue;
|
|
4586
|
+
for (const part of message.parts) if (part.type === "text" || part.type === "rich-text") {
|
|
4587
|
+
const text = readOptionalText(part.text);
|
|
4588
|
+
if (text) return truncateLabel(text);
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
return null;
|
|
4592
|
+
}
|
|
4593
|
+
function readMessageFromSummaryEvent(event) {
|
|
4594
|
+
switch (event.type) {
|
|
4595
|
+
case NcpEventType.MessageSent: return event.payload.message;
|
|
4596
|
+
case NcpEventType.MessageCompleted: return event.payload.message;
|
|
4597
|
+
default: return;
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
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
|
|
4613
|
+
}
|
|
4614
|
+
};
|
|
4615
|
+
}
|
|
4616
|
+
//#endregion
|
|
4617
|
+
//#region src/stores/ncp-agent-session-journal.store.ts
|
|
4618
|
+
var NcpAgentSessionJournalStore = class {
|
|
4619
|
+
sessions = /* @__PURE__ */ new Map();
|
|
4620
|
+
nextSeqBySession = /* @__PURE__ */ new Map();
|
|
4621
|
+
writeChains = /* @__PURE__ */ new Map();
|
|
4622
|
+
summaryIndex = null;
|
|
4623
|
+
constructor(journalDir) {
|
|
4624
|
+
this.journalDir = journalDir;
|
|
4625
|
+
}
|
|
4626
|
+
appendSessionEvent = async (params) => {
|
|
4627
|
+
const sessionId = normalizeNcpSessionId(params.session.sessionId);
|
|
4628
|
+
if (!sessionId) return;
|
|
4629
|
+
const next = (this.writeChains.get(sessionId) ?? Promise.resolve()).then(() => this.appendSessionEventNow({
|
|
4630
|
+
...params,
|
|
4631
|
+
session: {
|
|
4632
|
+
...params.session,
|
|
4633
|
+
sessionId
|
|
4634
|
+
}
|
|
4635
|
+
}));
|
|
4636
|
+
this.writeChains.set(sessionId, next.catch(() => void 0));
|
|
4637
|
+
await next;
|
|
4638
|
+
};
|
|
4639
|
+
getSession = async (sessionId) => {
|
|
4640
|
+
const normalizedSessionId = normalizeNcpSessionId(sessionId);
|
|
4641
|
+
if (!normalizedSessionId) return null;
|
|
4642
|
+
const cached = this.sessions.get(normalizedSessionId);
|
|
4643
|
+
if (cached) return structuredClone(cached.record);
|
|
4644
|
+
const loaded = await this.loadSession(normalizedSessionId);
|
|
4645
|
+
if (!loaded) return null;
|
|
4646
|
+
this.sessions.set(normalizedSessionId, loaded);
|
|
4647
|
+
return structuredClone(loaded.record);
|
|
4648
|
+
};
|
|
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
|
+
listSessionSummaries = async () => {
|
|
4658
|
+
return [...(await this.loadSummaryIndex()).values()].map((summary) => structuredClone(summary)).sort((left, right) => readNcpSessionSummaryActivityAt(right).localeCompare(readNcpSessionSummaryActivityAt(left)));
|
|
4659
|
+
};
|
|
4660
|
+
listSessionMessages = async (sessionId) => {
|
|
4661
|
+
const session = await this.getSession(sessionId);
|
|
4662
|
+
return session ? session.messages.map((message) => structuredClone(message)) : [];
|
|
4663
|
+
};
|
|
4664
|
+
replaceSession = async (record) => {
|
|
4665
|
+
const sessionId = normalizeNcpSessionId(record.sessionId);
|
|
4666
|
+
if (!sessionId) return;
|
|
4667
|
+
await this.ensureJournalDir();
|
|
4668
|
+
const nextRecord = structuredClone({
|
|
4669
|
+
...record,
|
|
4670
|
+
sessionId
|
|
4671
|
+
});
|
|
4672
|
+
const entries = [createNcpAgentSessionJournalMetadataEntry(nextRecord), ...nextRecord.messages.map((message, index) => ({
|
|
4673
|
+
_type: "event",
|
|
4674
|
+
version: 1,
|
|
4675
|
+
seq: index + 1,
|
|
4676
|
+
timestamp: message.timestamp ?? nextRecord.updatedAt,
|
|
4677
|
+
event: {
|
|
4678
|
+
type: NcpEventType.MessageSent,
|
|
4679
|
+
payload: {
|
|
4680
|
+
sessionId,
|
|
4681
|
+
message: structuredClone(message)
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
}))];
|
|
4685
|
+
await writeFile(this.sessionPath(sessionId), `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, "utf-8");
|
|
4686
|
+
const nextSeq = nextRecord.messages.length + 1;
|
|
4687
|
+
this.sessions.set(sessionId, {
|
|
4688
|
+
record: nextRecord,
|
|
4689
|
+
nextSeq
|
|
4690
|
+
});
|
|
4691
|
+
this.nextSeqBySession.set(sessionId, nextSeq);
|
|
4692
|
+
await this.upsertSummaryIndex(createNcpAgentSessionSummary(nextRecord));
|
|
4693
|
+
};
|
|
4694
|
+
deleteSession = async (sessionId) => {
|
|
4695
|
+
const normalizedSessionId = normalizeNcpSessionId(sessionId);
|
|
4696
|
+
if (!normalizedSessionId) return null;
|
|
4697
|
+
const existing = await this.getSession(normalizedSessionId);
|
|
4698
|
+
this.sessions.delete(normalizedSessionId);
|
|
4699
|
+
this.nextSeqBySession.delete(normalizedSessionId);
|
|
4700
|
+
try {
|
|
4701
|
+
await unlink(this.sessionPath(normalizedSessionId));
|
|
4702
|
+
} catch {}
|
|
4703
|
+
await this.removeSummaryIndex(normalizedSessionId);
|
|
4282
4704
|
return existing;
|
|
4283
4705
|
};
|
|
4706
|
+
hasSession = async (sessionId) => {
|
|
4707
|
+
const normalizedSessionId = normalizeNcpSessionId(sessionId);
|
|
4708
|
+
if (!normalizedSessionId) return false;
|
|
4709
|
+
return (await this.loadSummaryIndex()).has(normalizedSessionId);
|
|
4710
|
+
};
|
|
4711
|
+
appendSessionEventNow = async (params) => {
|
|
4712
|
+
const { event, session, updatedAt } = params;
|
|
4713
|
+
const { sessionId } = session;
|
|
4714
|
+
await this.ensureJournalDir();
|
|
4715
|
+
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
|
+
const nextSeq = this.nextSeqBySession.get(sessionId) ?? existing?.nextSeq ?? 1;
|
|
4718
|
+
const path = this.sessionPath(sessionId);
|
|
4719
|
+
if (!hasJournal) await appendFile(path, `${JSON.stringify(createNcpAgentSessionJournalMetadataEntry(session))}\n`, "utf-8");
|
|
4720
|
+
const entry = {
|
|
4721
|
+
_type: "event",
|
|
4722
|
+
version: 1,
|
|
4723
|
+
seq: nextSeq,
|
|
4724
|
+
timestamp: updatedAt,
|
|
4725
|
+
event: structuredClone(event)
|
|
4726
|
+
};
|
|
4727
|
+
await appendFile(path, `${JSON.stringify(entry)}\n`, "utf-8");
|
|
4728
|
+
this.nextSeqBySession.set(sessionId, nextSeq + 1);
|
|
4729
|
+
this.sessions.delete(sessionId);
|
|
4730
|
+
await this.upsertSummaryIndexForEvent({
|
|
4731
|
+
session,
|
|
4732
|
+
event,
|
|
4733
|
+
updatedAt
|
|
4734
|
+
});
|
|
4735
|
+
};
|
|
4736
|
+
loadSession = async (sessionId) => {
|
|
4737
|
+
let raw;
|
|
4738
|
+
try {
|
|
4739
|
+
raw = await readFile(this.sessionPath(sessionId), "utf-8");
|
|
4740
|
+
} catch {
|
|
4741
|
+
return null;
|
|
4742
|
+
}
|
|
4743
|
+
const { agentId, createdAt, events, metadata, nextSeq, updatedAt } = this.parseSessionJournal(raw);
|
|
4744
|
+
const messages = await replayNcpAgentSessionEvents(events);
|
|
4745
|
+
const record = {
|
|
4746
|
+
sessionId,
|
|
4747
|
+
...agentId ? { agentId } : {},
|
|
4748
|
+
messages,
|
|
4749
|
+
createdAt,
|
|
4750
|
+
updatedAt,
|
|
4751
|
+
metadata
|
|
4752
|
+
};
|
|
4753
|
+
this.nextSeqBySession.set(sessionId, nextSeq);
|
|
4754
|
+
return {
|
|
4755
|
+
record,
|
|
4756
|
+
nextSeq
|
|
4757
|
+
};
|
|
4758
|
+
};
|
|
4759
|
+
parseSessionJournal = (raw) => {
|
|
4760
|
+
let metadata = {};
|
|
4761
|
+
let agentId;
|
|
4762
|
+
let createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4763
|
+
let updatedAt = createdAt;
|
|
4764
|
+
let nextSeq = 1;
|
|
4765
|
+
const events = [];
|
|
4766
|
+
for (const line of raw.split("\n")) {
|
|
4767
|
+
if (!line.trim()) continue;
|
|
4768
|
+
const parsed = JSON.parse(line);
|
|
4769
|
+
if (!isRecord$1(parsed)) continue;
|
|
4770
|
+
if (parsed._type === "metadata") {
|
|
4771
|
+
metadata = isRecord$1(parsed.metadata) ? structuredClone(parsed.metadata) : {};
|
|
4772
|
+
agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
4773
|
+
createdAt = toIsoString(parsed.created_at, createdAt);
|
|
4774
|
+
updatedAt = toIsoString(parsed.updated_at, updatedAt);
|
|
4775
|
+
continue;
|
|
4776
|
+
}
|
|
4777
|
+
if (parsed._type === "event" && isRecord$1(parsed.event)) {
|
|
4778
|
+
const seq = Number(parsed.seq);
|
|
4779
|
+
nextSeq = Math.max(nextSeq, Number.isFinite(seq) ? Math.trunc(seq) + 1 : nextSeq);
|
|
4780
|
+
updatedAt = toIsoString(parsed.timestamp, updatedAt);
|
|
4781
|
+
events.push(structuredClone(parsed.event));
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
return {
|
|
4785
|
+
metadata,
|
|
4786
|
+
...agentId ? { agentId } : {},
|
|
4787
|
+
createdAt,
|
|
4788
|
+
updatedAt,
|
|
4789
|
+
nextSeq,
|
|
4790
|
+
events
|
|
4791
|
+
};
|
|
4792
|
+
};
|
|
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;
|
|
4805
|
+
};
|
|
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;
|
|
4823
|
+
};
|
|
4824
|
+
upsertSummaryIndex = async (summary) => {
|
|
4825
|
+
(await this.loadSummaryIndex()).set(summary.sessionId, structuredClone(summary));
|
|
4826
|
+
await this.persistSummaryIndex();
|
|
4827
|
+
};
|
|
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();
|
|
4839
|
+
};
|
|
4840
|
+
removeSummaryIndex = async (sessionId) => {
|
|
4841
|
+
(await this.loadSummaryIndex()).delete(sessionId);
|
|
4842
|
+
await this.persistSummaryIndex();
|
|
4843
|
+
};
|
|
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");
|
|
4851
|
+
};
|
|
4852
|
+
ensureJournalDir = async () => {
|
|
4853
|
+
await mkdir(this.journalDir, { recursive: true });
|
|
4854
|
+
};
|
|
4855
|
+
sessionPath = (sessionId) => {
|
|
4856
|
+
return join(this.journalDir, `${safeNcpSessionFilename(sessionId.replace(/:/g, "_"))}.jsonl`);
|
|
4857
|
+
};
|
|
4858
|
+
indexPath = () => resolve(this.journalDir, NCP_AGENT_SESSION_JOURNAL_INDEX_FILE);
|
|
4859
|
+
};
|
|
4860
|
+
//#endregion
|
|
4861
|
+
//#region src/contributions/session-activity-preview/utils/session-activity-preview-ncp-event.utils.ts
|
|
4862
|
+
const PREVIEW_TEXT_MAX_LENGTH = 160;
|
|
4863
|
+
function readSessionId(value) {
|
|
4864
|
+
if (typeof value !== "string") return null;
|
|
4865
|
+
const trimmed = value.trim();
|
|
4866
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
4867
|
+
}
|
|
4868
|
+
function compactPreviewText(value) {
|
|
4869
|
+
return value.replace(/\s+/g, " ").trim();
|
|
4870
|
+
}
|
|
4871
|
+
function truncatePreviewText(value) {
|
|
4872
|
+
const compacted = compactPreviewText(value);
|
|
4873
|
+
if (compacted.length <= PREVIEW_TEXT_MAX_LENGTH) return compacted;
|
|
4874
|
+
return compacted.slice(0, PREVIEW_TEXT_MAX_LENGTH - 1).trimEnd();
|
|
4875
|
+
}
|
|
4876
|
+
function readMessagePreviewText(message) {
|
|
4877
|
+
const chunks = [];
|
|
4878
|
+
for (const part of message.parts) if ((part.type === "text" || part.type === "rich-text") && part.text.trim()) chunks.push(part.text);
|
|
4879
|
+
const previewText = truncatePreviewText(chunks.join(" "));
|
|
4880
|
+
return previewText.length > 0 ? previewText : null;
|
|
4881
|
+
}
|
|
4882
|
+
function createProjection(sessionId, preview) {
|
|
4883
|
+
if (!sessionId) return null;
|
|
4884
|
+
return {
|
|
4885
|
+
sessionId,
|
|
4886
|
+
preview
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4889
|
+
function formatErrorStatus(error) {
|
|
4890
|
+
if (typeof error === "string" && error.trim()) return `运行出错:${truncatePreviewText(error)}`;
|
|
4891
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
4892
|
+
const message = error.message;
|
|
4893
|
+
if (typeof message === "string" && message.trim()) return `运行出错:${truncatePreviewText(message)}`;
|
|
4894
|
+
}
|
|
4895
|
+
return "运行出错";
|
|
4896
|
+
}
|
|
4897
|
+
function createSessionActivityPreviewFromNcpEvent(event, timestamp) {
|
|
4898
|
+
switch (event.type) {
|
|
4899
|
+
case NcpEventType.RunStarted: return createProjection(readSessionId(event.payload.sessionId), {
|
|
4900
|
+
state: "running",
|
|
4901
|
+
statusText: "正在处理...",
|
|
4902
|
+
timestamp
|
|
4903
|
+
});
|
|
4904
|
+
case NcpEventType.RunFinished: return createProjection(readSessionId(event.payload.sessionId), {
|
|
4905
|
+
state: "completed",
|
|
4906
|
+
timestamp
|
|
4907
|
+
});
|
|
4908
|
+
case NcpEventType.RunError: return createProjection(readSessionId(event.payload.sessionId), {
|
|
4909
|
+
state: "failed",
|
|
4910
|
+
statusText: formatErrorStatus(event.payload.error),
|
|
4911
|
+
timestamp
|
|
4912
|
+
});
|
|
4913
|
+
case NcpEventType.MessageSent: {
|
|
4914
|
+
const text = readMessagePreviewText(event.payload.message);
|
|
4915
|
+
if (!text || event.payload.message.role !== "user") return null;
|
|
4916
|
+
return createProjection(readSessionId(event.payload.sessionId), {
|
|
4917
|
+
state: "running",
|
|
4918
|
+
statusText: text,
|
|
4919
|
+
timestamp: event.payload.message.timestamp || timestamp
|
|
4920
|
+
});
|
|
4921
|
+
}
|
|
4922
|
+
case NcpEventType.MessageCompleted: {
|
|
4923
|
+
const text = readMessagePreviewText(event.payload.message);
|
|
4924
|
+
if (!text || event.payload.message.role !== "assistant") return null;
|
|
4925
|
+
return createProjection(readSessionId(event.payload.sessionId), {
|
|
4926
|
+
state: "completed",
|
|
4927
|
+
replyText: text,
|
|
4928
|
+
timestamp: event.payload.message.timestamp || timestamp
|
|
4929
|
+
});
|
|
4930
|
+
}
|
|
4931
|
+
case NcpEventType.MessageFailed: return createProjection(readSessionId(event.payload.sessionId), {
|
|
4932
|
+
state: "failed",
|
|
4933
|
+
statusText: formatErrorStatus(event.payload.error),
|
|
4934
|
+
timestamp
|
|
4935
|
+
});
|
|
4936
|
+
case NcpEventType.MessageToolCallStart: return createProjection(readSessionId(event.payload.sessionId), {
|
|
4937
|
+
state: "running",
|
|
4938
|
+
statusText: `正在调用工具:${event.payload.toolName}`,
|
|
4939
|
+
timestamp
|
|
4940
|
+
});
|
|
4941
|
+
case NcpEventType.MessageToolCallEnd:
|
|
4942
|
+
case NcpEventType.MessageToolCallResult: return createProjection(readSessionId(event.payload.sessionId), {
|
|
4943
|
+
state: "running",
|
|
4944
|
+
statusText: "工具调用完成",
|
|
4945
|
+
timestamp
|
|
4946
|
+
});
|
|
4947
|
+
default: return null;
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
//#endregion
|
|
4951
|
+
//#region src/contributions/session-activity-preview/utils/session-activity-preview-metadata.utils.ts
|
|
4952
|
+
const SESSION_ACTIVITY_PREVIEW_METADATA_KEY = "last_activity_preview";
|
|
4953
|
+
const SESSION_ACTIVITY_PREVIEW_STATES = new Set([
|
|
4954
|
+
"running",
|
|
4955
|
+
"completed",
|
|
4956
|
+
"failed",
|
|
4957
|
+
"idle"
|
|
4958
|
+
]);
|
|
4959
|
+
function isRecord(value) {
|
|
4960
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4961
|
+
}
|
|
4962
|
+
function readOptionalString(value) {
|
|
4963
|
+
if (typeof value !== "string") return;
|
|
4964
|
+
const trimmed = value.trim();
|
|
4965
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
4966
|
+
}
|
|
4967
|
+
function readSessionActivityPreviewMetadata(value) {
|
|
4968
|
+
if (!isRecord(value)) return null;
|
|
4969
|
+
const state = value.state;
|
|
4970
|
+
const timestamp = readOptionalString(value.timestamp);
|
|
4971
|
+
if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
|
|
4972
|
+
return {
|
|
4973
|
+
state,
|
|
4974
|
+
timestamp,
|
|
4975
|
+
...readOptionalString(value.statusText) ? { statusText: readOptionalString(value.statusText) } : {},
|
|
4976
|
+
...readOptionalString(value.replyText) ? { replyText: readOptionalString(value.replyText) } : {}
|
|
4977
|
+
};
|
|
4978
|
+
}
|
|
4979
|
+
function compareIsoTimestamp(left, right) {
|
|
4980
|
+
const leftTime = Date.parse(left);
|
|
4981
|
+
const rightTime = Date.parse(right);
|
|
4982
|
+
if (!Number.isFinite(leftTime) || !Number.isFinite(rightTime)) return left.localeCompare(right);
|
|
4983
|
+
return leftTime - rightTime;
|
|
4984
|
+
}
|
|
4985
|
+
function mergeSessionActivityPreview(current, incoming) {
|
|
4986
|
+
if (current && compareIsoTimestamp(incoming.timestamp, current.timestamp) < 0) return current;
|
|
4987
|
+
return {
|
|
4988
|
+
state: incoming.state,
|
|
4989
|
+
timestamp: incoming.timestamp,
|
|
4990
|
+
...incoming.statusText ?? (incoming.state === "completed" ? current?.statusText : void 0) ? { statusText: incoming.statusText ?? current?.statusText } : {},
|
|
4991
|
+
...incoming.replyText ?? current?.replyText ? { replyText: incoming.replyText ?? current?.replyText } : {}
|
|
4992
|
+
};
|
|
4993
|
+
}
|
|
4994
|
+
function areSessionActivityPreviewsEqual(left, right) {
|
|
4995
|
+
return Boolean(left) && left?.state === right.state && left.timestamp === right.timestamp && left.statusText === right.statusText && left.replyText === right.replyText;
|
|
4996
|
+
}
|
|
4997
|
+
function writeSessionActivityPreviewMetadata(metadata, projection) {
|
|
4998
|
+
const current = readSessionActivityPreviewMetadata(metadata?.[SESSION_ACTIVITY_PREVIEW_METADATA_KEY]);
|
|
4999
|
+
const next = mergeSessionActivityPreview(current, projection.preview);
|
|
5000
|
+
if (areSessionActivityPreviewsEqual(current, next)) return null;
|
|
5001
|
+
return {
|
|
5002
|
+
...metadata ?? {},
|
|
5003
|
+
[SESSION_ACTIVITY_PREVIEW_METADATA_KEY]: next
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
//#endregion
|
|
5007
|
+
//#region src/contributions/session-activity-preview/index.ts
|
|
5008
|
+
function formatBackgroundError(error) {
|
|
5009
|
+
if (error instanceof Error) return error.stack ?? error.message;
|
|
5010
|
+
return String(error);
|
|
5011
|
+
}
|
|
5012
|
+
function readMetadata(value) {
|
|
5013
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
5014
|
+
return value;
|
|
5015
|
+
}
|
|
5016
|
+
var SessionActivityPreviewContribution = class {
|
|
5017
|
+
unsubscribeNcpEvent = null;
|
|
5018
|
+
stopped = true;
|
|
5019
|
+
constructor(kernel) {
|
|
5020
|
+
this.kernel = kernel;
|
|
5021
|
+
}
|
|
5022
|
+
start = () => {
|
|
5023
|
+
if (this.unsubscribeNcpEvent) return;
|
|
5024
|
+
this.stopped = false;
|
|
5025
|
+
this.unsubscribeNcpEvent = this.kernel.eventBus.on(eventKeys.ncpEvent, (event) => {
|
|
5026
|
+
const projection = createSessionActivityPreviewFromNcpEvent(event, (/* @__PURE__ */ new Date()).toISOString());
|
|
5027
|
+
if (!projection) return;
|
|
5028
|
+
this.updatePreview(projection);
|
|
5029
|
+
});
|
|
5030
|
+
};
|
|
5031
|
+
dispose = () => {
|
|
5032
|
+
this.unsubscribeNcpEvent?.();
|
|
5033
|
+
this.unsubscribeNcpEvent = null;
|
|
5034
|
+
this.stopped = true;
|
|
5035
|
+
};
|
|
5036
|
+
updatePreview = (projection) => {
|
|
5037
|
+
this.persistPreview(projection).catch((error) => {
|
|
5038
|
+
console.error(`[session-activity-preview] failed to update ${projection.sessionId}: ${formatBackgroundError(error)}`);
|
|
5039
|
+
});
|
|
5040
|
+
};
|
|
5041
|
+
persistPreview = async (projection) => {
|
|
5042
|
+
if (this.stopped) return;
|
|
5043
|
+
const summary = await this.kernel.ncpSessionApi.getSession(projection.sessionId);
|
|
5044
|
+
if (!summary || this.stopped) return;
|
|
5045
|
+
const nextMetadata = writeSessionActivityPreviewMetadata(readMetadata(summary.metadata), projection);
|
|
5046
|
+
if (!nextMetadata) return;
|
|
5047
|
+
await this.kernel.ncpSessionApi.updateSession(projection.sessionId, { metadata: nextMetadata });
|
|
5048
|
+
};
|
|
4284
5049
|
};
|
|
4285
5050
|
//#endregion
|
|
4286
5051
|
//#region src/app/nextclaw-kernel.ts
|
|
@@ -4326,7 +5091,8 @@ var NextclawKernel = class {
|
|
|
4326
5091
|
learningLoop;
|
|
4327
5092
|
sessionLifecycleEvents;
|
|
4328
5093
|
ncpAgentSessionStore;
|
|
4329
|
-
|
|
5094
|
+
ncpAgentSessionJournalStore;
|
|
5095
|
+
contributions;
|
|
4330
5096
|
constructor(options = {}) {
|
|
4331
5097
|
const sessionsDir = resolveKernelSessionsDir(options);
|
|
4332
5098
|
this.sessions = new SessionManager({ sessionsDir });
|
|
@@ -4336,7 +5102,11 @@ var NextclawKernel = class {
|
|
|
4336
5102
|
sessionsDir,
|
|
4337
5103
|
onSessionUpdated: this.publishSessionUpdated
|
|
4338
5104
|
});
|
|
4339
|
-
this.
|
|
5105
|
+
this.ncpAgentSessionJournalStore = new NcpAgentSessionJournalStore(resolve(sessionsDir, ".ncp-agent-journal"));
|
|
5106
|
+
this.ncpAgentSessionStore = new NcpAgentSessionStoreAdapter(this.sessions, {
|
|
5107
|
+
journalStore: this.ncpAgentSessionJournalStore,
|
|
5108
|
+
onSessionUpdated: this.sessionSearch.handleSessionUpdated
|
|
5109
|
+
});
|
|
4340
5110
|
this.sessionRequests = new SessionRequestManager({
|
|
4341
5111
|
sessions: this.sessions,
|
|
4342
5112
|
dispatcher: createAgentRuntimeSessionRequestDispatcher({
|
|
@@ -4360,11 +5130,6 @@ var NextclawKernel = class {
|
|
|
4360
5130
|
channels: this.channels,
|
|
4361
5131
|
providerManager: this.llmProviders
|
|
4362
5132
|
});
|
|
4363
|
-
this.ncpSessionApi = new NcpSessionApiService({
|
|
4364
|
-
eventBus: this.eventBus,
|
|
4365
|
-
getConfig: this.configManager.loadConfig,
|
|
4366
|
-
sessionManager: this.sessions
|
|
4367
|
-
});
|
|
4368
5133
|
this.agentRuntimeManager = new AgentRuntimeManager({
|
|
4369
5134
|
bus: this.messageBus,
|
|
4370
5135
|
providerManager: this.llmProviders,
|
|
@@ -4381,30 +5146,34 @@ var NextclawKernel = class {
|
|
|
4381
5146
|
llmUsage: this.llmUsage,
|
|
4382
5147
|
onSessionUpdated: this.publishSessionUpdated
|
|
4383
5148
|
});
|
|
5149
|
+
this.ncpSessionApi = new NcpSessionApiService({
|
|
5150
|
+
eventBus: this.eventBus,
|
|
5151
|
+
getConfig: this.configManager.loadConfig,
|
|
5152
|
+
isLiveSessionRunning: this.agentRuntimeManager.isLiveSessionRunning,
|
|
5153
|
+
ncpAgentSessionStore: this.ncpAgentSessionStore,
|
|
5154
|
+
sessionManager: this.sessions
|
|
5155
|
+
});
|
|
4384
5156
|
this.learningLoop = new LearningLoopManager({
|
|
4385
5157
|
eventBus: this.eventBus,
|
|
4386
5158
|
sessionManager: this.sessions,
|
|
4387
5159
|
sessionRequester: this.sessionRequests,
|
|
4388
5160
|
resolveLearningLoopConfig: () => readLearningLoopRuntimeConfig(this.configManager.loadConfig())
|
|
4389
5161
|
});
|
|
5162
|
+
this.contributions = [new SessionActivityPreviewContribution(this)];
|
|
4390
5163
|
}
|
|
4391
5164
|
start = async () => {
|
|
4392
|
-
this.
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
this.startPromise = null;
|
|
4398
|
-
throw error;
|
|
4399
|
-
});
|
|
4400
|
-
return this.startPromise;
|
|
5165
|
+
this.ncpSessionApi.start();
|
|
5166
|
+
this.sessionSearch.start();
|
|
5167
|
+
for (const contribution of this.contributions) contribution.start();
|
|
5168
|
+
this.agentRuntimeManager.bootstrap();
|
|
5169
|
+
this.learningLoop.start();
|
|
4401
5170
|
};
|
|
4402
5171
|
dispose = async () => {
|
|
4403
5172
|
this.learningLoop.dispose();
|
|
5173
|
+
for (const contribution of this.contributions) contribution.dispose();
|
|
4404
5174
|
this.ncpSessionApi.dispose();
|
|
4405
5175
|
await this.agentRuntimeManager.dispose();
|
|
4406
5176
|
await this.sessionSearch.dispose();
|
|
4407
|
-
this.startPromise = null;
|
|
4408
5177
|
};
|
|
4409
5178
|
publishSessionUpdated = (sessionKey) => {
|
|
4410
5179
|
this.sessionLifecycleEvents.publishSessionUpdated(sessionKey);
|