@nextclaw/kernel 0.5.4 → 0.6.0
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.map +1 -1
- package/dist/index.js +363 -216
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t as UpdateManifestReader } from "./update-manifest.types-C0qPrjGQ.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SILENT_REPLY_TOKEN, SessionProjectContextResolver, SessionSearchService, SkillsLoader, THINKING_LEVELS, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createRuntimeChildEnv, createTypingStopControlMessage, diffConfigPaths, ensureDir, evaluateSilentReply, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, isNextclawControlMessage, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeAgentProfileId, normalizeInlineSecretRefs, normalizeModelThinkingCapability, normalizeProviderModelConfig, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveNextclawSelfManageGuidePaths, resolveProviderRuntime, resolveSessionProjectContext, resolveSessionWorkspacePath, resolveThinkingLevel, sanitizeOutboundAssistantContent, saveConfig, summarizeSessionRequestTask, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
|
|
3
|
+
import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderRegistry, ReadFileTool, RequestedSkillsMetadataReader, SILENT_REPLY_TOKEN, SessionProjectContextResolver, SessionSearchService, SkillsLoader, THINKING_LEVELS, ViewImageTool, WebFetchTool, WebSearchTool, WriteFileTool, buildCompressingCompactionCheckpoint, buildConfigSchema, buildContextWindowSnapshot, buildReloadPlan, buildSessionRequestToolResult, buildToolCatalogEntries, createAgentProfile, createAssistantStreamDeltaControlMessage, createAssistantStreamResetControlMessage, createCompletedSessionRequest, createFailedSessionRequest, createRunningSessionRequest, createRuntimeChildEnv, createTypingStopControlMessage, diffConfigPaths, ensureDir, evaluateSilentReply, expandHome, findEffectiveAgentProfile, getConfigPath, getDataDir, getDataPath, getSessionsPath, getWorkspacePath, getWorkspacePathFromConfig, isNextclawControlMessage, loadConfig, mergeExtensionConfigView, modelSupportsVision, normalizeAgentProfileId, normalizeInlineSecretRefs, normalizeModelThinkingCapability, normalizeProviderModelConfig, normalizeToolParams, parseAgentScopedSessionKey, parseThinkingLevel, readCompressedContextCompactionCheckpoint, readOptionalString, readParentSessionId, readSessionProjectRoot, redactConfigObject, removeAgentProfile, resolveConfigSecrets, resolveDefaultAgentProfileId, resolveEffectiveAgentProfiles, resolveNextclawSelfManageGuidePaths, resolveProviderRuntime, resolveSessionProjectContext, resolveSessionWorkspacePath, resolveThinkingLevel, sanitizeOutboundAssistantContent, saveConfig, summarizeSessionRequestTask, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
|
|
4
4
|
import { NcpEventType, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
|
|
5
5
|
import { LocalAssetStore, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
|
|
6
6
|
import { createHash, createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
@@ -71,6 +71,40 @@ function readRequestedAgentId(metadata) {
|
|
|
71
71
|
return normalizeAgentProfileId(metadata.agent_id) || normalizeAgentProfileId(metadata.agentId) || null;
|
|
72
72
|
}
|
|
73
73
|
//#endregion
|
|
74
|
+
//#region src/utils/agent-run-metadata.utils.ts
|
|
75
|
+
const AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY = "run_spec";
|
|
76
|
+
const LEGACY_RUN_METADATA_KEYS = [
|
|
77
|
+
"account_id",
|
|
78
|
+
"agent_id",
|
|
79
|
+
"chat_id",
|
|
80
|
+
"preferred_model",
|
|
81
|
+
"project_root",
|
|
82
|
+
"runtime",
|
|
83
|
+
"sender_id",
|
|
84
|
+
"session_key",
|
|
85
|
+
"session_type"
|
|
86
|
+
];
|
|
87
|
+
function stripLegacyRunMetadata(metadata) {
|
|
88
|
+
const nextMetadata = structuredClone(metadata);
|
|
89
|
+
for (const key of LEGACY_RUN_METADATA_KEYS) delete nextMetadata[key];
|
|
90
|
+
return nextMetadata;
|
|
91
|
+
}
|
|
92
|
+
function buildRunMetadata(params) {
|
|
93
|
+
const { message, metadata, route } = params;
|
|
94
|
+
return {
|
|
95
|
+
...stripLegacyRunMetadata({
|
|
96
|
+
...message.metadata ?? {},
|
|
97
|
+
...metadata ?? {}
|
|
98
|
+
}),
|
|
99
|
+
channel: message.channel,
|
|
100
|
+
chatId: message.chatId,
|
|
101
|
+
accountId: route.accountId,
|
|
102
|
+
agentId: route.agentId,
|
|
103
|
+
sessionKey: route.sessionKey,
|
|
104
|
+
senderId: message.senderId
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
74
108
|
//#region src/utils/ncp-message-bridge.utils.ts
|
|
75
109
|
function normalizeString$1(value) {
|
|
76
110
|
if (typeof value !== "string") return null;
|
|
@@ -517,6 +551,7 @@ var AgentRunContextCompactionManager = class {
|
|
|
517
551
|
if (Object.keys(result.metadataPatch).length > 0) await this.sessionManager.patchSessionMetadata(sessionId, result.metadataPatch);
|
|
518
552
|
if (!result.timelineMessage) return [];
|
|
519
553
|
return [{
|
|
554
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
520
555
|
type: NcpEventType.MessageSent,
|
|
521
556
|
payload: {
|
|
522
557
|
sessionId,
|
|
@@ -584,6 +619,37 @@ function readOptionalString$10(value) {
|
|
|
584
619
|
if (typeof value !== "string") return;
|
|
585
620
|
return value.trim() || void 0;
|
|
586
621
|
}
|
|
622
|
+
function readRunStartedAt(event, fallback) {
|
|
623
|
+
if (event.type !== NcpEventType.RunStarted) return fallback;
|
|
624
|
+
return event.payload.startedAt ?? event.occurredAt ?? fallback;
|
|
625
|
+
}
|
|
626
|
+
function createCompletedAssistantMessageEvent(params) {
|
|
627
|
+
return {
|
|
628
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
629
|
+
type: NcpEventType.MessageCompleted,
|
|
630
|
+
payload: {
|
|
631
|
+
sessionId: params.sessionId,
|
|
632
|
+
message: params.message,
|
|
633
|
+
correlationId: params.correlationId
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function createSyntheticRunErrorEvent(params) {
|
|
638
|
+
const { correlationId, error, runId, sessionId, startedAt } = params;
|
|
639
|
+
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
640
|
+
return {
|
|
641
|
+
occurredAt: endedAt,
|
|
642
|
+
type: NcpEventType.RunError,
|
|
643
|
+
payload: {
|
|
644
|
+
error: error instanceof Error ? error.message : String(error),
|
|
645
|
+
runId,
|
|
646
|
+
sessionId,
|
|
647
|
+
correlationId,
|
|
648
|
+
startedAt,
|
|
649
|
+
endedAt
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
587
653
|
function readSessionMaterialization(metadata) {
|
|
588
654
|
const value = metadata[CHAT_SESSION_MATERIALIZATION_METADATA_KEY];
|
|
589
655
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
@@ -613,6 +679,46 @@ function findCompletedAssistantMessage(messages, messageId) {
|
|
|
613
679
|
function readMessageTask(message) {
|
|
614
680
|
return message.parts.flatMap((part) => (part.type === "text" || part.type === "rich-text" || part.type === "reasoning") && part.text.trim() ? [part.text.trim()] : [])[0] ?? "Session";
|
|
615
681
|
}
|
|
682
|
+
function resolveRunSpec(params) {
|
|
683
|
+
const { agentManager, configManager, request, runId, session } = params;
|
|
684
|
+
const modelSource = request.model !== void 0 ? "request" : session.model !== void 0 ? "session" : "default";
|
|
685
|
+
const model = request.model ?? session.model ?? configManager.getDefaultModel();
|
|
686
|
+
return {
|
|
687
|
+
modelSource,
|
|
688
|
+
spec: {
|
|
689
|
+
runId,
|
|
690
|
+
agentId: request.agentId ?? session.agentId ?? agentManager.getDefaultAgentId(),
|
|
691
|
+
model,
|
|
692
|
+
maxTokens: request.maxTokens ?? configManager.getModelMaxTokens(model),
|
|
693
|
+
thinkingEffort: request.thinkingEffort ?? session.thinkingEffort ?? null,
|
|
694
|
+
correlationId: request.correlationId
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function attachRunSpecMetadata(params) {
|
|
699
|
+
const { message, modelSource, request, session, spec, startedAt } = params;
|
|
700
|
+
const metadata = structuredClone(message.metadata ?? {});
|
|
701
|
+
metadata[AGENT_RUN_MESSAGE_RUN_SPEC_METADATA_KEY] = {
|
|
702
|
+
version: 1,
|
|
703
|
+
runId: spec.runId,
|
|
704
|
+
startedAt,
|
|
705
|
+
sessionId: session.sessionId,
|
|
706
|
+
agentRuntimeId: session.agentRuntimeId,
|
|
707
|
+
agentId: spec.agentId,
|
|
708
|
+
model: spec.model,
|
|
709
|
+
modelSource,
|
|
710
|
+
requestedModel: request.model ?? null,
|
|
711
|
+
maxTokens: spec.maxTokens,
|
|
712
|
+
thinkingEffort: spec.thinkingEffort,
|
|
713
|
+
projectRoot: request.projectRoot ?? session.projectRoot ?? null,
|
|
714
|
+
workingDir: session.workingDir ?? null,
|
|
715
|
+
correlationId: spec.correlationId ?? null
|
|
716
|
+
};
|
|
717
|
+
return {
|
|
718
|
+
...message,
|
|
719
|
+
metadata
|
|
720
|
+
};
|
|
721
|
+
}
|
|
616
722
|
var AgentRunRequestManager = class {
|
|
617
723
|
cleanups = [];
|
|
618
724
|
started = false;
|
|
@@ -658,16 +764,10 @@ var AgentRunRequestManager = class {
|
|
|
658
764
|
send = async (request) => {
|
|
659
765
|
const session = await this.getOrCreateSessionForRequest(request);
|
|
660
766
|
const sessionRun = this.sessionRunManager.getSessionRun(session.sessionId) ?? await this.sessionRunManager.createSessionRun(session.sessionId);
|
|
661
|
-
const
|
|
767
|
+
const baseMessage = {
|
|
662
768
|
...request.message,
|
|
663
769
|
sessionId: session.sessionId
|
|
664
770
|
};
|
|
665
|
-
const providerRequest = {
|
|
666
|
-
...request,
|
|
667
|
-
sessionId: session.sessionId,
|
|
668
|
-
message
|
|
669
|
-
};
|
|
670
|
-
sessionRun.inbox.enqueue(message);
|
|
671
771
|
let stopPublishingRunStatus = () => void 0;
|
|
672
772
|
stopPublishingRunStatus = sessionRun.onStatusChange((status) => {
|
|
673
773
|
this.eventBus.emit(eventKeys.sessionRunStatus, {
|
|
@@ -680,6 +780,7 @@ var AgentRunRequestManager = class {
|
|
|
680
780
|
if (status === "idle") stopPublishingRunStatus();
|
|
681
781
|
});
|
|
682
782
|
this.cleanups.push(stopPublishingRunStatus);
|
|
783
|
+
const requestRunStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
683
784
|
const activeRun = (() => {
|
|
684
785
|
try {
|
|
685
786
|
return sessionRun.beginRun();
|
|
@@ -688,16 +789,27 @@ var AgentRunRequestManager = class {
|
|
|
688
789
|
throw error;
|
|
689
790
|
}
|
|
690
791
|
})();
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
792
|
+
const { modelSource, spec } = resolveRunSpec({
|
|
793
|
+
agentManager: this.agentManager,
|
|
794
|
+
configManager: this.configManager,
|
|
795
|
+
request,
|
|
694
796
|
runId: activeRun.runId,
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
797
|
+
session
|
|
798
|
+
});
|
|
799
|
+
const message = attachRunSpecMetadata({
|
|
800
|
+
message: baseMessage,
|
|
801
|
+
modelSource,
|
|
802
|
+
request,
|
|
803
|
+
session,
|
|
804
|
+
spec,
|
|
805
|
+
startedAt: requestRunStartedAt
|
|
806
|
+
});
|
|
807
|
+
const providerRequest = {
|
|
808
|
+
...request,
|
|
809
|
+
sessionId: session.sessionId,
|
|
810
|
+
message
|
|
700
811
|
};
|
|
812
|
+
sessionRun.inbox.enqueue(message);
|
|
701
813
|
const runtime = this.agentRuntimeManager.getOrCreate({
|
|
702
814
|
agentRuntimeId: session.agentRuntimeId,
|
|
703
815
|
session,
|
|
@@ -707,6 +819,7 @@ var AgentRunRequestManager = class {
|
|
|
707
819
|
const tools = await this.toolProviderManager.buildTools(providerRequest);
|
|
708
820
|
let messageCompletedSeen = false;
|
|
709
821
|
let runtimeFailed = false;
|
|
822
|
+
let runStartedAt = requestRunStartedAt;
|
|
710
823
|
lastValueFrom(from(runtime.run(spec, {
|
|
711
824
|
contextBlocks,
|
|
712
825
|
session,
|
|
@@ -716,18 +829,16 @@ var AgentRunRequestManager = class {
|
|
|
716
829
|
})).pipe(tap((event) => {
|
|
717
830
|
const eventsToPublish = [];
|
|
718
831
|
if (event.type === NcpEventType.RunError) runtimeFailed = true;
|
|
832
|
+
runStartedAt = readRunStartedAt(event, runStartedAt);
|
|
719
833
|
if (event.type === NcpEventType.MessageCompleted) messageCompletedSeen = true;
|
|
720
834
|
if (event.type === NcpEventType.RunFinished && !messageCompletedSeen) {
|
|
721
835
|
const message = findCompletedAssistantMessage(sessionRun.getSnapshot().messages, event.payload.messageId);
|
|
722
836
|
if (!message) throw new Error(`Run finished without a final assistant message for session "${session.sessionId}".`);
|
|
723
|
-
eventsToPublish.push({
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
correlationId: event.payload.correlationId
|
|
729
|
-
}
|
|
730
|
-
});
|
|
837
|
+
eventsToPublish.push(createCompletedAssistantMessageEvent({
|
|
838
|
+
sessionId: event.payload.sessionId ?? session.sessionId,
|
|
839
|
+
message,
|
|
840
|
+
correlationId: event.payload.correlationId
|
|
841
|
+
}));
|
|
731
842
|
messageCompletedSeen = true;
|
|
732
843
|
}
|
|
733
844
|
eventsToPublish.push(event);
|
|
@@ -737,15 +848,13 @@ var AgentRunRequestManager = class {
|
|
|
737
848
|
});
|
|
738
849
|
}), catchError(async (error) => {
|
|
739
850
|
runtimeFailed = true;
|
|
740
|
-
const event = {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
};
|
|
851
|
+
const event = createSyntheticRunErrorEvent({
|
|
852
|
+
error,
|
|
853
|
+
runId: spec.runId,
|
|
854
|
+
sessionId: session.sessionId,
|
|
855
|
+
correlationId: spec.correlationId,
|
|
856
|
+
startedAt: runStartedAt
|
|
857
|
+
});
|
|
749
858
|
await sessionRun.applyEvents([event]);
|
|
750
859
|
this.eventBus.emit(eventKeys.ncpEvent, event, {
|
|
751
860
|
emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2816,11 +2925,11 @@ var ExtensionManager = class {
|
|
|
2816
2925
|
//#endregion
|
|
2817
2926
|
//#region src/utils/model-message-vision.utils.ts
|
|
2818
2927
|
const IMAGE_OMITTED_TEXT = "[Image omitted: the selected model is not configured for vision input.]";
|
|
2819
|
-
function isRecord$
|
|
2928
|
+
function isRecord$11(value) {
|
|
2820
2929
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2821
2930
|
}
|
|
2822
2931
|
function isImageContentPart(value) {
|
|
2823
|
-
if (!isRecord$
|
|
2932
|
+
if (!isRecord$11(value)) return false;
|
|
2824
2933
|
const type = value.type;
|
|
2825
2934
|
return type === "image_url" || type === "input_image";
|
|
2826
2935
|
}
|
|
@@ -2836,7 +2945,7 @@ function normalizeContentWithoutVision(content) {
|
|
|
2836
2945
|
};
|
|
2837
2946
|
});
|
|
2838
2947
|
if (!sawImage) return content;
|
|
2839
|
-
const textParts = parts.filter((part) => isRecord$
|
|
2948
|
+
const textParts = parts.filter((part) => isRecord$11(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
|
|
2840
2949
|
if (textParts.length === parts.length) return textParts.join("\n\n");
|
|
2841
2950
|
return parts;
|
|
2842
2951
|
}
|
|
@@ -3399,7 +3508,7 @@ function safeNcpSessionFilename(value) {
|
|
|
3399
3508
|
function normalizeNcpAgentId(agentId) {
|
|
3400
3509
|
return agentId?.trim().toLowerCase() || void 0;
|
|
3401
3510
|
}
|
|
3402
|
-
function isRecord$
|
|
3511
|
+
function isRecord$10(value) {
|
|
3403
3512
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3404
3513
|
}
|
|
3405
3514
|
function toIsoString(value, fallback) {
|
|
@@ -3470,6 +3579,7 @@ async function replayNcpAgentSessionEvents(events) {
|
|
|
3470
3579
|
}
|
|
3471
3580
|
function createReplayEvent(event, toolResultsByCallId) {
|
|
3472
3581
|
const replayEvent = structuredClone(event);
|
|
3582
|
+
const occurredAt = readReplayEventOccurredAt(replayEvent);
|
|
3473
3583
|
const replayMessage = readMessageFromSummaryEvent(replayEvent);
|
|
3474
3584
|
const legacyCompactionMessageId = readLegacyContextCompactionMessageId(replayMessage);
|
|
3475
3585
|
if (replayMessage && legacyCompactionMessageId) replayMessage.id = legacyCompactionMessageId;
|
|
@@ -3477,12 +3587,17 @@ function createReplayEvent(event, toolResultsByCallId) {
|
|
|
3477
3587
|
if (replayEvent.type === "session.snapshot.message" || replayEvent.type === NcpEventType.MessageCompleted) {
|
|
3478
3588
|
replayEvent.payload.message = mergeReplayCompletedToolResults(replayEvent.payload.message, toolResultsByCallId);
|
|
3479
3589
|
return {
|
|
3590
|
+
occurredAt,
|
|
3480
3591
|
type: NcpEventType.MessageSent,
|
|
3481
3592
|
payload: replayEvent.payload
|
|
3482
3593
|
};
|
|
3483
3594
|
}
|
|
3484
3595
|
return replayEvent;
|
|
3485
3596
|
}
|
|
3597
|
+
function readReplayEventOccurredAt(event) {
|
|
3598
|
+
if (!("occurredAt" in event) || typeof event.occurredAt !== "string") return;
|
|
3599
|
+
return event.occurredAt;
|
|
3600
|
+
}
|
|
3486
3601
|
function mergeReplayCompletedToolResults(message, toolResultsByCallId) {
|
|
3487
3602
|
let changed = false;
|
|
3488
3603
|
const parts = message.parts.map((part) => {
|
|
@@ -3503,7 +3618,7 @@ function mergeReplayCompletedToolResults(message, toolResultsByCallId) {
|
|
|
3503
3618
|
} : message;
|
|
3504
3619
|
}
|
|
3505
3620
|
function readLegacyContextCompactionMessageId(message) {
|
|
3506
|
-
const checkpoint = isRecord$
|
|
3621
|
+
const checkpoint = isRecord$10(message?.metadata?.checkpoint) ? message.metadata.checkpoint : null;
|
|
3507
3622
|
const checkpointId = typeof checkpoint?.id === "string" ? checkpoint.id : "";
|
|
3508
3623
|
const coveredCount = checkpoint?.coveredSessionMessageCount;
|
|
3509
3624
|
const legacyId = `${message?.sessionId}:service:context-compaction:${checkpointId}`;
|
|
@@ -3514,6 +3629,7 @@ function createReplayStreamingBootstrapEvent(event, knownMessageIds) {
|
|
|
3514
3629
|
if (!messageId || knownMessageIds.has(messageId)) return null;
|
|
3515
3630
|
knownMessageIds.add(messageId);
|
|
3516
3631
|
return {
|
|
3632
|
+
occurredAt: event.occurredAt,
|
|
3517
3633
|
type: NcpEventType.MessageSent,
|
|
3518
3634
|
payload: {
|
|
3519
3635
|
sessionId: readEventSessionId$2(event),
|
|
@@ -3533,11 +3649,11 @@ function rememberReplayMessageId(event, knownMessageIds) {
|
|
|
3533
3649
|
if (message?.id) knownMessageIds.add(message.id);
|
|
3534
3650
|
}
|
|
3535
3651
|
function readEventSessionId$2(event) {
|
|
3536
|
-
const sessionId = ("payload" in event && isRecord$
|
|
3652
|
+
const sessionId = ("payload" in event && isRecord$10(event.payload) ? event.payload : null)?.sessionId;
|
|
3537
3653
|
return typeof sessionId === "string" ? sessionId : "";
|
|
3538
3654
|
}
|
|
3539
3655
|
function readReplayPayloadTimestamp(event) {
|
|
3540
|
-
const payload = "payload" in event && isRecord$
|
|
3656
|
+
const payload = "payload" in event && isRecord$10(event.payload) ? event.payload : null;
|
|
3541
3657
|
const timestamp = typeof payload?.timestamp === "string" ? payload.timestamp : "";
|
|
3542
3658
|
return Number.isFinite(Date.parse(timestamp)) ? new Date(timestamp).toISOString() : null;
|
|
3543
3659
|
}
|
|
@@ -3817,7 +3933,7 @@ function createSessionActivityPreviewFromNcpEvent(event, timestamp, options = {}
|
|
|
3817
3933
|
return createProjection(readSessionId(event.payload.sessionId), {
|
|
3818
3934
|
state: "completed",
|
|
3819
3935
|
replyText: text,
|
|
3820
|
-
timestamp
|
|
3936
|
+
timestamp
|
|
3821
3937
|
});
|
|
3822
3938
|
}
|
|
3823
3939
|
case NcpEventType.MessageFailed: return createProjection(readSessionId(event.payload.sessionId), {
|
|
@@ -3852,7 +3968,7 @@ const SESSION_ACTIVITY_PREVIEW_STATES = new Set([
|
|
|
3852
3968
|
"failed",
|
|
3853
3969
|
"idle"
|
|
3854
3970
|
]);
|
|
3855
|
-
function isRecord$
|
|
3971
|
+
function isRecord$9(value) {
|
|
3856
3972
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3857
3973
|
}
|
|
3858
3974
|
function readOptionalString$7(value) {
|
|
@@ -3861,7 +3977,7 @@ function readOptionalString$7(value) {
|
|
|
3861
3977
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
3862
3978
|
}
|
|
3863
3979
|
function readSessionActivityPreviewMetadata(value) {
|
|
3864
|
-
if (!isRecord$
|
|
3980
|
+
if (!isRecord$9(value)) return null;
|
|
3865
3981
|
const state = value.state;
|
|
3866
3982
|
const timestamp = readOptionalString$7(value.timestamp);
|
|
3867
3983
|
if (!SESSION_ACTIVITY_PREVIEW_STATES.has(state) || !timestamp) return null;
|
|
@@ -4526,12 +4642,12 @@ var PanelAppCapabilityGrantStore = class {
|
|
|
4526
4642
|
};
|
|
4527
4643
|
};
|
|
4528
4644
|
function normalizeStoreData$2(value) {
|
|
4529
|
-
if (!isRecord$
|
|
4645
|
+
if (!isRecord$8(value) || value.version !== 1 || !isRecord$8(value.grants)) return structuredClone(EMPTY_GRANTS$2);
|
|
4530
4646
|
const grants = {};
|
|
4531
4647
|
for (const [callerKey, callerValue] of Object.entries(value.grants)) {
|
|
4532
|
-
if (!isRecord$
|
|
4648
|
+
if (!isRecord$8(callerValue) || !isRecord$8(callerValue.capabilities)) continue;
|
|
4533
4649
|
const capabilities = {};
|
|
4534
|
-
for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$
|
|
4650
|
+
for (const [capability, grantValue] of Object.entries(callerValue.capabilities)) if (isRecord$8(grantValue) && typeof grantValue.grantedAt === "string") capabilities[capability] = { grantedAt: grantValue.grantedAt };
|
|
4535
4651
|
grants[callerKey] = { capabilities };
|
|
4536
4652
|
}
|
|
4537
4653
|
return {
|
|
@@ -4542,7 +4658,7 @@ function normalizeStoreData$2(value) {
|
|
|
4542
4658
|
function getCallerKey(caller) {
|
|
4543
4659
|
return `${caller.surface}:${caller.appId}`;
|
|
4544
4660
|
}
|
|
4545
|
-
function isRecord$
|
|
4661
|
+
function isRecord$8(value) {
|
|
4546
4662
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4547
4663
|
}
|
|
4548
4664
|
function isMissingFileError$3(error) {
|
|
@@ -4588,19 +4704,19 @@ var PanelAppClientGrantStore = class {
|
|
|
4588
4704
|
};
|
|
4589
4705
|
};
|
|
4590
4706
|
function normalizeStoreData$1(value) {
|
|
4591
|
-
if (!isRecord$
|
|
4707
|
+
if (!isRecord$7(value) || value.version !== 1 || !isRecord$7(value.grants)) return structuredClone(EMPTY_GRANTS$1);
|
|
4592
4708
|
const grants = {};
|
|
4593
|
-
for (const [appId, grant] of Object.entries(value.grants)) if (isRecord$
|
|
4709
|
+
for (const [appId, grant] of Object.entries(value.grants)) if (isRecord$7(grant) && typeof grant.grantedAt === "string") grants[appId] = { grantedAt: grant.grantedAt };
|
|
4594
4710
|
return {
|
|
4595
4711
|
grants,
|
|
4596
4712
|
version: 1
|
|
4597
4713
|
};
|
|
4598
4714
|
}
|
|
4599
|
-
function isRecord$
|
|
4715
|
+
function isRecord$7(value) {
|
|
4600
4716
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4601
4717
|
}
|
|
4602
4718
|
function isMissingFileError$2(error) {
|
|
4603
|
-
return isRecord$
|
|
4719
|
+
return isRecord$7(error) && error.code === "ENOENT";
|
|
4604
4720
|
}
|
|
4605
4721
|
//#endregion
|
|
4606
4722
|
//#region src/services/agent-run-client.service.ts
|
|
@@ -4819,7 +4935,7 @@ const PANEL_APP_AGENT_MAX_CONTEXT_CHARS = 8e4;
|
|
|
4819
4935
|
function normalizePanelAppGenerateObjectInput(input) {
|
|
4820
4936
|
const peerId = input.peerId.trim();
|
|
4821
4937
|
const prompt = input.prompt.trim();
|
|
4822
|
-
if (!peerId || !prompt || !isRecord$
|
|
4938
|
+
if (!peerId || !prompt || !isRecord$6(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
|
|
4823
4939
|
if (prompt.length > PANEL_APP_AGENT_MAX_PROMPT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject prompt is too large");
|
|
4824
4940
|
if (stringifyJsonValue(input.context ?? null).length > PANEL_APP_AGENT_MAX_CONTEXT_CHARS) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "generateObject context is too large");
|
|
4825
4941
|
return {
|
|
@@ -4960,7 +5076,7 @@ function readStructuredResultEvent(event, resultToolCallId) {
|
|
|
4960
5076
|
};
|
|
4961
5077
|
}
|
|
4962
5078
|
function assertToolResultContent(content) {
|
|
4963
|
-
if (!isRecord$
|
|
5079
|
+
if (!isRecord$6(content) || content.ok !== false || !isRecord$6(content.error)) return;
|
|
4964
5080
|
if (content.error.code === "invalid_tool_arguments") throw new PanelAppError("AGENT_OBJECT_RESULT_SCHEMA_INVALID", "agent object result did not match the schema");
|
|
4965
5081
|
throw new PanelAppError("AGENT_OBJECT_REQUEST_FAILED", typeof content.error.message === "string" ? content.error.message : "agent object request failed");
|
|
4966
5082
|
}
|
|
@@ -4976,7 +5092,7 @@ function stringifyJsonValue(value) {
|
|
|
4976
5092
|
throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "value is not JSON serializable");
|
|
4977
5093
|
}
|
|
4978
5094
|
}
|
|
4979
|
-
function isRecord$
|
|
5095
|
+
function isRecord$6(value) {
|
|
4980
5096
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4981
5097
|
}
|
|
4982
5098
|
//#endregion
|
|
@@ -5250,7 +5366,7 @@ function parsePanelAppFolderManifest(raw) {
|
|
|
5250
5366
|
} catch (error) {
|
|
5251
5367
|
throw new Error(`panel-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
5252
5368
|
}
|
|
5253
|
-
if (!isRecord$
|
|
5369
|
+
if (!isRecord$5(parsed)) throw new Error("panel-app.json must contain an object.");
|
|
5254
5370
|
const id = readOptionalString$6(parsed, "id");
|
|
5255
5371
|
if (id && !PANEL_APP_ID_PATTERN.test(id)) throw new Error("panel app id must be kebab-case.");
|
|
5256
5372
|
return {
|
|
@@ -5337,7 +5453,7 @@ function readStringArray$1(value, key) {
|
|
|
5337
5453
|
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`panel app ${key} must be a string array.`);
|
|
5338
5454
|
return [...new Set(value.map((entry) => entry.trim()).filter(Boolean))];
|
|
5339
5455
|
}
|
|
5340
|
-
function isRecord$
|
|
5456
|
+
function isRecord$5(value) {
|
|
5341
5457
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5342
5458
|
}
|
|
5343
5459
|
function normalizeTextValue(value) {
|
|
@@ -6174,12 +6290,12 @@ var ServiceActionGrantStore = class {
|
|
|
6174
6290
|
};
|
|
6175
6291
|
};
|
|
6176
6292
|
function normalizeStoreData(value) {
|
|
6177
|
-
if (!isRecord$
|
|
6293
|
+
if (!isRecord$4(value) || value.version !== 1 || !isRecord$4(value.grants)) return structuredClone(EMPTY_GRANTS);
|
|
6178
6294
|
const grants = {};
|
|
6179
6295
|
for (const [callerKey, callerValue] of Object.entries(value.grants)) {
|
|
6180
|
-
if (!isRecord$
|
|
6296
|
+
if (!isRecord$4(callerValue) || !isRecord$4(callerValue.actions)) continue;
|
|
6181
6297
|
const actions = {};
|
|
6182
|
-
for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$
|
|
6298
|
+
for (const [actionId, actionValue] of Object.entries(callerValue.actions)) if (isRecord$4(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
|
|
6183
6299
|
grantedAt: actionValue.grantedAt,
|
|
6184
6300
|
risk: actionValue.risk
|
|
6185
6301
|
};
|
|
@@ -6193,11 +6309,11 @@ function normalizeStoreData(value) {
|
|
|
6193
6309
|
function isServiceActionRisk(value) {
|
|
6194
6310
|
return value === "read" || value === "write" || value === "external" || value === "dangerous";
|
|
6195
6311
|
}
|
|
6196
|
-
function isRecord$
|
|
6312
|
+
function isRecord$4(value) {
|
|
6197
6313
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6198
6314
|
}
|
|
6199
6315
|
function isMissingFileError(error) {
|
|
6200
|
-
return isRecord$
|
|
6316
|
+
return isRecord$4(error) && error.code === "ENOENT";
|
|
6201
6317
|
}
|
|
6202
6318
|
//#endregion
|
|
6203
6319
|
//#region src/utils/service-app-manifest.utils.ts
|
|
@@ -6222,7 +6338,7 @@ function parseServiceAppManifest(raw) {
|
|
|
6222
6338
|
} catch (error) {
|
|
6223
6339
|
throw new Error(`service-app.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
6224
6340
|
}
|
|
6225
|
-
if (!isRecord$
|
|
6341
|
+
if (!isRecord$3(parsed)) throw new Error("service-app.json must contain an object.");
|
|
6226
6342
|
const id = readRequiredString$3(parsed, "id");
|
|
6227
6343
|
if (!SERVICE_APP_ID_PATTERN.test(id)) throw new Error("service app id must be kebab-case.");
|
|
6228
6344
|
const protocol = readOptionalString$5(parsed, "protocol") ?? "mcp";
|
|
@@ -6240,16 +6356,16 @@ function parseServiceAppManifest(raw) {
|
|
|
6240
6356
|
}
|
|
6241
6357
|
function readManifestActions(value) {
|
|
6242
6358
|
if (value === void 0) throw new Error("service app actions are required.");
|
|
6243
|
-
if (!isRecord$
|
|
6359
|
+
if (!isRecord$3(value)) throw new Error("service app actions must be an object.");
|
|
6244
6360
|
if (Object.keys(value).length === 0) throw new Error("service app actions cannot be empty.");
|
|
6245
6361
|
const actions = {};
|
|
6246
6362
|
for (const [name, action] of Object.entries(value)) {
|
|
6247
6363
|
if (!name.trim()) throw new Error("service app action name cannot be empty.");
|
|
6248
|
-
if (!isRecord$
|
|
6364
|
+
if (!isRecord$3(action)) throw new Error(`service app action ${name} must be an object.`);
|
|
6249
6365
|
const risk = readOptionalString$5(action, "risk");
|
|
6250
6366
|
if (risk !== void 0 && !SERVICE_ACTION_RISKS.has(risk)) throw new Error(`service app action ${name} has invalid risk.`);
|
|
6251
6367
|
const inputSchema = action.inputSchema;
|
|
6252
|
-
if (inputSchema !== void 0 && !isRecord$
|
|
6368
|
+
if (inputSchema !== void 0 && !isRecord$3(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
|
|
6253
6369
|
actions[name] = {
|
|
6254
6370
|
risk,
|
|
6255
6371
|
title: readOptionalString$5(action, "title"),
|
|
@@ -6277,7 +6393,7 @@ function readStringArray(value, key) {
|
|
|
6277
6393
|
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`service app ${key} must be a string array.`);
|
|
6278
6394
|
return value;
|
|
6279
6395
|
}
|
|
6280
|
-
function isRecord$
|
|
6396
|
+
function isRecord$3(value) {
|
|
6281
6397
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6282
6398
|
}
|
|
6283
6399
|
//#endregion
|
|
@@ -6700,7 +6816,7 @@ function parseFrontmatterBlock(raw) {
|
|
|
6700
6816
|
function parseYamlFrontmatter(raw) {
|
|
6701
6817
|
try {
|
|
6702
6818
|
const parsed = parse(raw);
|
|
6703
|
-
return isRecord$
|
|
6819
|
+
return isRecord$2(parsed) ? parsed : {};
|
|
6704
6820
|
} catch (error) {
|
|
6705
6821
|
const message = error instanceof Error ? error.message : String(error);
|
|
6706
6822
|
throw new Error(`Invalid SKILL.md frontmatter: ${message}`);
|
|
@@ -6712,7 +6828,7 @@ function readString$3(record, ...names) {
|
|
|
6712
6828
|
}
|
|
6713
6829
|
function readLocalizedTextMap(record, ...names) {
|
|
6714
6830
|
const value = readValue(record, names);
|
|
6715
|
-
if (!isRecord$
|
|
6831
|
+
if (!isRecord$2(value)) return;
|
|
6716
6832
|
const localized = Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0).map(([locale, text]) => [normalizeLocaleTag(locale), text.trim()]));
|
|
6717
6833
|
return Object.keys(localized).length > 0 ? localized : void 0;
|
|
6718
6834
|
}
|
|
@@ -6736,7 +6852,7 @@ function normalizeFrontmatterKey(raw) {
|
|
|
6736
6852
|
function normalizeLocaleTag(raw) {
|
|
6737
6853
|
return raw.trim().toLowerCase();
|
|
6738
6854
|
}
|
|
6739
|
-
function isRecord$
|
|
6855
|
+
function isRecord$2(value) {
|
|
6740
6856
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6741
6857
|
}
|
|
6742
6858
|
//#endregion
|
|
@@ -6902,7 +7018,7 @@ var NcpAgentSessionMetadataStore = class {
|
|
|
6902
7018
|
read = async (sessionId, activitySnapshot) => {
|
|
6903
7019
|
try {
|
|
6904
7020
|
const parsed = JSON.parse(await readFile(this.metadataPath(sessionId), "utf-8"));
|
|
6905
|
-
if (!isRecord$
|
|
7021
|
+
if (!isRecord$10(parsed) || parsed._type !== "metadata" || !isRecord$10(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
|
|
6906
7022
|
const createdAt = toIsoString(parsed.created_at, activitySnapshot.createdAt);
|
|
6907
7023
|
const agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
6908
7024
|
return {
|
|
@@ -7003,11 +7119,11 @@ var NcpAgentSessionSummaryIndexStore = class {
|
|
|
7003
7119
|
function serializeJournalEntry(entry) {
|
|
7004
7120
|
const serialized = JSON.stringify(entry);
|
|
7005
7121
|
if (!serialized) throw new Error("ncp agent session journal entry serialization produced empty output");
|
|
7006
|
-
if (!isRecord$
|
|
7122
|
+
if (!isRecord$10(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
|
|
7007
7123
|
return serialized;
|
|
7008
7124
|
}
|
|
7009
7125
|
function attachJournalTimestamp(event, timestamp) {
|
|
7010
|
-
if (!("payload" in event) || !isRecord$
|
|
7126
|
+
if (!("payload" in event) || !isRecord$10(event.payload)) return event;
|
|
7011
7127
|
return {
|
|
7012
7128
|
...event,
|
|
7013
7129
|
payload: {
|
|
@@ -7231,15 +7347,15 @@ var NcpAgentSessionJournalStore = class {
|
|
|
7231
7347
|
console.warn(`[ncp-agent-session-journal] skipped corrupted journal line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7232
7348
|
continue;
|
|
7233
7349
|
}
|
|
7234
|
-
if (!isRecord$
|
|
7350
|
+
if (!isRecord$10(parsed)) continue;
|
|
7235
7351
|
if (parsed._type === "metadata") {
|
|
7236
|
-
metadata = isRecord$
|
|
7352
|
+
metadata = isRecord$10(parsed.metadata) ? structuredClone(parsed.metadata) : {};
|
|
7237
7353
|
agentId = normalizeNcpAgentId(typeof parsed.agent_id === "string" ? parsed.agent_id : void 0);
|
|
7238
7354
|
createdAt = toIsoString(parsed.created_at, createdAt);
|
|
7239
7355
|
updatedAt = toIsoString(parsed.updated_at, updatedAt);
|
|
7240
7356
|
continue;
|
|
7241
7357
|
}
|
|
7242
|
-
if (parsed._type === "event" && isRecord$
|
|
7358
|
+
if (parsed._type === "event" && isRecord$10(parsed.event)) {
|
|
7243
7359
|
const seq = Number(parsed.seq);
|
|
7244
7360
|
nextSeq = Math.max(nextSeq, Number.isFinite(seq) ? Math.trunc(seq) + 1 : nextSeq);
|
|
7245
7361
|
const eventTimestamp = toIsoString(parsed.timestamp, updatedAt);
|
|
@@ -8349,6 +8465,7 @@ var NcpAgentRuntimeWrapper = class {
|
|
|
8349
8465
|
return this.runtime;
|
|
8350
8466
|
};
|
|
8351
8467
|
toMessageSentEvents = (messages, spec, sessionId) => messages.map((message) => ({
|
|
8468
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8352
8469
|
type: NcpEventType.MessageSent,
|
|
8353
8470
|
payload: {
|
|
8354
8471
|
sessionId,
|
|
@@ -8601,9 +8718,11 @@ const createToolCallStyleContextProvider = () => staticBlock([
|
|
|
8601
8718
|
const createInlineInteractiveSurfaceContextProvider = () => staticBlock([
|
|
8602
8719
|
"## Inline Interactive Surfaces",
|
|
8603
8720
|
"Do not make every UI an inline card. Choose inline only when the intended result is a compact, immediately usable card or short interaction; use the side panel for normal Panel Apps, long reading, rich editing, file browsing, large tables, multi-page workflows, or sustained workspaces.",
|
|
8604
|
-
"
|
|
8721
|
+
"Inline Panel App display is Markdown-only: in the final reply, output a `nextclaw-inline` fenced JSON block so the display remains message content.",
|
|
8722
|
+
"`show_panel_app` is side-panel only. Never call `show_panel_app` for inline display, including when the user asks which Panel Apps are suitable for inline display or says \"show/display them inline\".",
|
|
8723
|
+
"For ordinary local HTML files or page prototypes, call `show_file` with `path` and `viewer=\"rendered\"`; use `viewer=\"source\"` when the user needs to inspect source text. Do not convert a plain HTML file into a Panel App just to preview it.",
|
|
8605
8724
|
"A Panel Card must be designed card-first: prefer a landscape composition where width carries the main information and the card is wider than it is tall; collapse to one column only in narrow containers. Core value must be visible in the first 220-420px, with no horizontal scrolling, no reliance on document-level internal scrolling, compact controls, at most one primary action, clear loading/empty/error states, and an obvious expand path for details.",
|
|
8606
|
-
"Typical Panel Card fits: weather cards, calculators, timers, checklists, pickers, compact forms, previews, and small dashboards. If the UI needs more space than a card,
|
|
8725
|
+
"Typical Panel Card fits: weather cards, calculators, timers, checklists, pickers, compact forms, previews, and small dashboards. If the UI needs more space than a card, use the side panel instead. Inline hosts may pass `nextclawDisplayMode=card` and `nextclawPlacement=inline`; use those hints to render a compact card layout instead of a full page."
|
|
8607
8726
|
]);
|
|
8608
8727
|
const createChatComposerTokensContextProvider = () => staticBlock([
|
|
8609
8728
|
"## Chat Composer Tokens",
|
|
@@ -8758,7 +8877,7 @@ var ProjectContextProvider = class {
|
|
|
8758
8877
|
//#endregion
|
|
8759
8878
|
//#region src/contributions/context-provider/providers/reply-format-context.provider.ts
|
|
8760
8879
|
var ReplyFormatContextProvider = class {
|
|
8761
|
-
provide = (_request) => ["## Reply Formatting Contract\nGoal:
|
|
8880
|
+
provide = (_request) => ["## Reply Formatting Contract\nGoal: openable files in user-visible replies must be clickable, and inert inline display declarations are only for content that should appear as part of the reply.\nFile links: use Markdown links only, with a plain text label and an openable href: [MEMORY.md](MEMORY.md), [file](packages/example/file.ts), [notes.md](/Users/example/Documents/notes.md). Use project-relative hrefs for files under the active/session project root, and absolute hrefs for local files outside it.\nInline display: when the final reply should include a non-clickable inline display placeholder, output a fenced `nextclaw-inline` JSON block:\n```nextclaw-inline\n{\"target\":{\"type\":\"panel_app\",\"payload\":{\"appId\":\"timer\"}},\"title\":\"Timer\"}\n```\nSupported targets are `panel_app`, `json`, `file`, and `url`. Prefer `panel_app` for inline Panel App display; use `file` and `url` only as non-clickable placeholders when a clickable link is not intended; use `json` for inert JSON snapshots.\nIt is display-only: no opening, executing, or tool action. Never call `show_panel_app` for inline display; `show_panel_app` is only for immediately opening a Panel App outside the final reply in the side panel. Use Markdown links for clickable resources and show_file/show_url/show_panel_app tools only when you want the UI to immediately show or run content outside the final reply.\nForbidden forms: bare file names or paths, inline-code file names, bold-only file names, code-styled link labels, code blocks for file references, action semantics inside `nextclaw-inline`, tool calls for inline display, and unlinked comma-separated file lists.\nExamples: bad `MEMORY.md` -> good [MEMORY.md](MEMORY.md); bad `memory/` -> good [memory/](memory/); bad `2026-03-07.md` / `feishu-notes.md` -> good [2026-03-07.md](memory/2026-03-07.md) / [feishu-notes.md](memory/feishu-notes.md).\nSelf-check before sending: scan the final visible reply for local file names or paths. If every concrete file cannot be linked or intentionally represented by `nextclaw-inline`, remove the exact names and summarize instead."];
|
|
8762
8881
|
};
|
|
8763
8882
|
//#endregion
|
|
8764
8883
|
//#region src/contributions/context-provider/providers/skills-context.provider.ts
|
|
@@ -9115,6 +9234,7 @@ var ContextWindowContribution = class {
|
|
|
9115
9234
|
if (this.lastPublishedSignatureBySession.get(sessionId) === signature) return;
|
|
9116
9235
|
this.lastPublishedSignatureBySession.set(sessionId, signature);
|
|
9117
9236
|
this.kernel.eventBus.emit(eventKeys.ncpEvent, {
|
|
9237
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9118
9238
|
type: NcpEventType.ContextWindowUpdated,
|
|
9119
9239
|
payload: {
|
|
9120
9240
|
contextWindow,
|
|
@@ -9314,6 +9434,10 @@ var CoreToolProvider = class {
|
|
|
9314
9434
|
new WriteFileTool(allowedDir),
|
|
9315
9435
|
new EditFileTool(allowedDir),
|
|
9316
9436
|
new ListDirTool(allowedDir),
|
|
9437
|
+
new ViewImageTool({
|
|
9438
|
+
allowedDir,
|
|
9439
|
+
workingDir: workspace
|
|
9440
|
+
}),
|
|
9317
9441
|
execTool,
|
|
9318
9442
|
new WebSearchTool(searchConfig),
|
|
9319
9443
|
new WebFetchTool(),
|
|
@@ -9577,7 +9701,7 @@ var SessionRequestTool = class {
|
|
|
9577
9701
|
};
|
|
9578
9702
|
//#endregion
|
|
9579
9703
|
//#region src/tools/session-search.tools.ts
|
|
9580
|
-
function isRecord$
|
|
9704
|
+
function isRecord$1(value) {
|
|
9581
9705
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9582
9706
|
}
|
|
9583
9707
|
function readOptionalInteger(value) {
|
|
@@ -9627,7 +9751,7 @@ var SessionSearchTool = class {
|
|
|
9627
9751
|
return issues;
|
|
9628
9752
|
};
|
|
9629
9753
|
execute = async (args) => {
|
|
9630
|
-
if (!isRecord$
|
|
9754
|
+
if (!isRecord$1(args)) throw new Error("session_search requires an object argument.");
|
|
9631
9755
|
const issues = this.validateArgs(args);
|
|
9632
9756
|
if (issues.length > 0) throw new Error(issues.join(" "));
|
|
9633
9757
|
return this.queryService.search({
|
|
@@ -9811,90 +9935,89 @@ var SessionToolProvider = class {
|
|
|
9811
9935
|
};
|
|
9812
9936
|
//#endregion
|
|
9813
9937
|
//#region src/tools/show-content.tools.ts
|
|
9814
|
-
const
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9938
|
+
const FILE_PURPOSES = [
|
|
9939
|
+
"read",
|
|
9940
|
+
"preview",
|
|
9941
|
+
"edit"
|
|
9942
|
+
];
|
|
9943
|
+
const URL_PURPOSES = ["read", "preview"];
|
|
9944
|
+
const PANEL_APP_PURPOSES = ["preview", "interact"];
|
|
9945
|
+
const FILE_VIEWERS = [
|
|
9946
|
+
"auto",
|
|
9947
|
+
"source",
|
|
9948
|
+
"rendered"
|
|
9949
|
+
];
|
|
9818
9950
|
function readRequiredString(value, key) {
|
|
9819
9951
|
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${key} must be a non-empty string.`);
|
|
9820
9952
|
return value.trim();
|
|
9821
9953
|
}
|
|
9822
9954
|
function readOptionalString$1(value) {
|
|
9823
9955
|
if (typeof value !== "string") return;
|
|
9824
|
-
|
|
9825
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
9956
|
+
return value.trim() || void 0;
|
|
9826
9957
|
}
|
|
9827
9958
|
function readOptionalPositiveInteger(value, key) {
|
|
9828
9959
|
if (typeof value === "undefined") return;
|
|
9829
9960
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new Error(`${key} must be a positive integer.`);
|
|
9830
9961
|
return value;
|
|
9831
9962
|
}
|
|
9832
|
-
function
|
|
9963
|
+
function readOptionalEnum(value, key, allowed) {
|
|
9833
9964
|
const normalized = readOptionalString$1(value);
|
|
9834
9965
|
if (!normalized) return;
|
|
9835
|
-
if (normalized
|
|
9836
|
-
|
|
9837
|
-
}
|
|
9838
|
-
function readPlacement(value) {
|
|
9839
|
-
const normalized = readOptionalString$1(value);
|
|
9840
|
-
if (!normalized) return;
|
|
9841
|
-
if (normalized === "inline" || normalized === "side_panel") return normalized;
|
|
9842
|
-
throw new Error("placement must be \"inline\" or \"side_panel\".");
|
|
9843
|
-
}
|
|
9844
|
-
function readPayload(params) {
|
|
9845
|
-
if (!isRecord$1(params.payload)) throw new Error("payload must be an object.");
|
|
9846
|
-
return params.payload;
|
|
9966
|
+
if (allowed.includes(normalized)) return normalized;
|
|
9967
|
+
const expected = allowed.map((item) => `"${item}"`).join(", ");
|
|
9968
|
+
throw new Error(`${key} must be ${expected}.`);
|
|
9847
9969
|
}
|
|
9848
9970
|
function readUrl(value) {
|
|
9849
|
-
const url = readRequiredString(value, "
|
|
9971
|
+
const url = readRequiredString(value, "url");
|
|
9850
9972
|
let parsed;
|
|
9851
9973
|
try {
|
|
9852
9974
|
parsed = new URL(url);
|
|
9853
9975
|
} catch {
|
|
9854
|
-
throw new Error("
|
|
9976
|
+
throw new Error("url must be a valid URL.");
|
|
9855
9977
|
}
|
|
9856
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("
|
|
9978
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("url must use http or https.");
|
|
9857
9979
|
return parsed.toString();
|
|
9858
9980
|
}
|
|
9859
|
-
function
|
|
9981
|
+
function readCommonRequestFields(params, allowedPurposes) {
|
|
9982
|
+
return {
|
|
9983
|
+
title: readOptionalString$1(params.title),
|
|
9984
|
+
purpose: readOptionalEnum(params.purpose, "purpose", allowedPurposes)
|
|
9985
|
+
};
|
|
9986
|
+
}
|
|
9987
|
+
function normalizeShowFileArgs(args) {
|
|
9860
9988
|
const params = normalizeToolParams(args);
|
|
9861
|
-
|
|
9862
|
-
const payload = readPayload(params);
|
|
9863
|
-
const title = readOptionalString$1(params.title);
|
|
9864
|
-
const purpose = readPurpose(params.purpose);
|
|
9865
|
-
const placement = readPlacement(params.placement);
|
|
9866
|
-
if (type === "file") return {
|
|
9989
|
+
return {
|
|
9867
9990
|
target: {
|
|
9868
|
-
type,
|
|
9991
|
+
type: "file",
|
|
9869
9992
|
payload: {
|
|
9870
|
-
path: readRequiredString(
|
|
9871
|
-
line: readOptionalPositiveInteger(
|
|
9872
|
-
column: readOptionalPositiveInteger(
|
|
9993
|
+
path: readRequiredString(params.path, "path"),
|
|
9994
|
+
line: readOptionalPositiveInteger(params.line, "line"),
|
|
9995
|
+
column: readOptionalPositiveInteger(params.column, "column"),
|
|
9996
|
+
viewer: readOptionalEnum(params.viewer, "viewer", FILE_VIEWERS)
|
|
9873
9997
|
}
|
|
9874
9998
|
},
|
|
9875
|
-
|
|
9876
|
-
purpose,
|
|
9877
|
-
placement
|
|
9999
|
+
...readCommonRequestFields(params, FILE_PURPOSES)
|
|
9878
10000
|
};
|
|
9879
|
-
|
|
10001
|
+
}
|
|
10002
|
+
function normalizeShowUrlArgs(args) {
|
|
10003
|
+
const params = normalizeToolParams(args);
|
|
10004
|
+
return {
|
|
9880
10005
|
target: {
|
|
9881
|
-
type,
|
|
9882
|
-
payload: { url: readUrl(
|
|
10006
|
+
type: "url",
|
|
10007
|
+
payload: { url: readUrl(params.url) }
|
|
9883
10008
|
},
|
|
9884
|
-
|
|
9885
|
-
purpose,
|
|
9886
|
-
placement
|
|
10009
|
+
...readCommonRequestFields(params, URL_PURPOSES)
|
|
9887
10010
|
};
|
|
9888
|
-
|
|
10011
|
+
}
|
|
10012
|
+
function normalizeShowPanelAppArgs(args) {
|
|
10013
|
+
const params = normalizeToolParams(args);
|
|
10014
|
+
return {
|
|
9889
10015
|
target: {
|
|
9890
|
-
type,
|
|
9891
|
-
payload: { appId: readRequiredString(
|
|
10016
|
+
type: "panel_app",
|
|
10017
|
+
payload: { appId: readRequiredString(params.appId, "appId") }
|
|
9892
10018
|
},
|
|
9893
|
-
|
|
9894
|
-
purpose,
|
|
9895
|
-
placement
|
|
10019
|
+
...readCommonRequestFields(params, PANEL_APP_PURPOSES)
|
|
9896
10020
|
};
|
|
9897
|
-
throw new Error("type must be \"file\", \"url\", or \"panel_app\".");
|
|
9898
10021
|
}
|
|
9899
10022
|
function summarizeTarget(target) {
|
|
9900
10023
|
if (target.type === "file") return target.payload.path;
|
|
@@ -9909,63 +10032,25 @@ function createShowContentEventPayload(request, context) {
|
|
|
9909
10032
|
target: request.target,
|
|
9910
10033
|
title: request.title,
|
|
9911
10034
|
purpose: request.purpose,
|
|
9912
|
-
placement:
|
|
9913
|
-
};
|
|
9914
|
-
}
|
|
9915
|
-
var ShowContentTool = class {
|
|
9916
|
-
name = SHOW_CONTENT_TOOL_NAME;
|
|
9917
|
-
description = [
|
|
9918
|
-
"Show file, URL, or panel app content in the current chat UI.",
|
|
9919
|
-
"placement=\"inline\" embeds a lightweight panel_app as an interactive chat card so the user can try it directly in the conversation.",
|
|
9920
|
-
"placement=\"side_panel\" opens content in the right side panel for larger reading, editing, or sustained workflows.",
|
|
9921
|
-
"Choose the placement yourself: after creating a lightweight Panel App such as a weather card, calculator, timer, checklist, picker, form, preview, or small dashboard, call this tool with placement=\"inline\" without waiting for the user to ask to see it.",
|
|
9922
|
-
"Omit placement only when preserving the existing default side panel behavior is intentional."
|
|
9923
|
-
].join(" ");
|
|
9924
|
-
parameters = {
|
|
9925
|
-
type: "object",
|
|
9926
|
-
properties: {
|
|
9927
|
-
type: {
|
|
9928
|
-
type: "string",
|
|
9929
|
-
enum: [
|
|
9930
|
-
"file",
|
|
9931
|
-
"url",
|
|
9932
|
-
"panel_app"
|
|
9933
|
-
],
|
|
9934
|
-
description: "Content target type."
|
|
9935
|
-
},
|
|
9936
|
-
title: {
|
|
9937
|
-
type: "string",
|
|
9938
|
-
description: "Optional title for the shown content."
|
|
9939
|
-
},
|
|
9940
|
-
purpose: {
|
|
9941
|
-
type: "string",
|
|
9942
|
-
enum: [
|
|
9943
|
-
"read",
|
|
9944
|
-
"preview",
|
|
9945
|
-
"edit",
|
|
9946
|
-
"interact"
|
|
9947
|
-
],
|
|
9948
|
-
description: "Optional user intent for the content."
|
|
9949
|
-
},
|
|
9950
|
-
placement: {
|
|
9951
|
-
type: "string",
|
|
9952
|
-
enum: ["inline", "side_panel"],
|
|
9953
|
-
description: "Optional display placement. \"inline\" embeds a lightweight panel app as an interactive chat card; \"side_panel\" opens it in the right panel for larger workflows. Choose the appropriate placement yourself; defaults to the existing side panel behavior when omitted."
|
|
9954
|
-
},
|
|
9955
|
-
payload: {
|
|
9956
|
-
type: "object",
|
|
9957
|
-
description: "Type-specific fields: file={path,line,column}; url={url}; panel_app={appId}.",
|
|
9958
|
-
additionalProperties: true
|
|
9959
|
-
}
|
|
9960
|
-
},
|
|
9961
|
-
required: ["type", "payload"],
|
|
9962
|
-
additionalProperties: false
|
|
10035
|
+
placement: "side_panel"
|
|
9963
10036
|
};
|
|
9964
|
-
|
|
10037
|
+
}
|
|
10038
|
+
var ShowContentDisplayTool = class {
|
|
10039
|
+
constructor(eventBus, spec) {
|
|
9965
10040
|
this.eventBus = eventBus;
|
|
10041
|
+
this.spec = spec;
|
|
10042
|
+
}
|
|
10043
|
+
get name() {
|
|
10044
|
+
return this.spec.name;
|
|
10045
|
+
}
|
|
10046
|
+
get description() {
|
|
10047
|
+
return this.spec.description;
|
|
10048
|
+
}
|
|
10049
|
+
get parameters() {
|
|
10050
|
+
return this.spec.parameters;
|
|
9966
10051
|
}
|
|
9967
10052
|
execute = async (args, context) => {
|
|
9968
|
-
const request =
|
|
10053
|
+
const request = this.spec.normalize(args);
|
|
9969
10054
|
this.eventBus.emit(eventKeys.uiShowContent, createShowContentEventPayload(request, context), { source: "kernel" });
|
|
9970
10055
|
return {
|
|
9971
10056
|
ok: true,
|
|
@@ -9974,13 +10059,108 @@ var ShowContentTool = class {
|
|
|
9974
10059
|
};
|
|
9975
10060
|
};
|
|
9976
10061
|
};
|
|
10062
|
+
const SHOW_CONTENT_TOOL_SPECS = [
|
|
10063
|
+
{
|
|
10064
|
+
name: "show_file",
|
|
10065
|
+
description: "Show a local file in the current chat UI. Use viewer=\"rendered\" for rendered HTML/page previews and viewer=\"source\" for source text.",
|
|
10066
|
+
parameters: {
|
|
10067
|
+
type: "object",
|
|
10068
|
+
properties: {
|
|
10069
|
+
path: {
|
|
10070
|
+
type: "string",
|
|
10071
|
+
description: "Local file path to show."
|
|
10072
|
+
},
|
|
10073
|
+
title: {
|
|
10074
|
+
type: "string",
|
|
10075
|
+
description: "Optional title for the shown content."
|
|
10076
|
+
},
|
|
10077
|
+
purpose: {
|
|
10078
|
+
type: "string",
|
|
10079
|
+
enum: FILE_PURPOSES,
|
|
10080
|
+
description: "Optional user intent."
|
|
10081
|
+
},
|
|
10082
|
+
line: {
|
|
10083
|
+
type: "integer",
|
|
10084
|
+
minimum: 1,
|
|
10085
|
+
description: "Optional 1-based line number."
|
|
10086
|
+
},
|
|
10087
|
+
column: {
|
|
10088
|
+
type: "integer",
|
|
10089
|
+
minimum: 1,
|
|
10090
|
+
description: "Optional 1-based column number."
|
|
10091
|
+
},
|
|
10092
|
+
viewer: {
|
|
10093
|
+
type: "string",
|
|
10094
|
+
enum: FILE_VIEWERS,
|
|
10095
|
+
description: "Optional file viewer mode."
|
|
10096
|
+
}
|
|
10097
|
+
},
|
|
10098
|
+
required: ["path"],
|
|
10099
|
+
additionalProperties: false
|
|
10100
|
+
},
|
|
10101
|
+
normalize: normalizeShowFileArgs
|
|
10102
|
+
},
|
|
10103
|
+
{
|
|
10104
|
+
name: "show_url",
|
|
10105
|
+
description: "Show an http or https URL in the current chat UI browser. Use this for local development servers such as Vite, Next.js, Storybook, or any running web app URL.",
|
|
10106
|
+
parameters: {
|
|
10107
|
+
type: "object",
|
|
10108
|
+
properties: {
|
|
10109
|
+
url: {
|
|
10110
|
+
type: "string",
|
|
10111
|
+
description: "HTTP or HTTPS URL to show."
|
|
10112
|
+
},
|
|
10113
|
+
title: {
|
|
10114
|
+
type: "string",
|
|
10115
|
+
description: "Optional title for the shown content."
|
|
10116
|
+
},
|
|
10117
|
+
purpose: {
|
|
10118
|
+
type: "string",
|
|
10119
|
+
enum: URL_PURPOSES,
|
|
10120
|
+
description: "Optional user intent."
|
|
10121
|
+
}
|
|
10122
|
+
},
|
|
10123
|
+
required: ["url"],
|
|
10124
|
+
additionalProperties: false
|
|
10125
|
+
},
|
|
10126
|
+
normalize: normalizeShowUrlArgs
|
|
10127
|
+
},
|
|
10128
|
+
{
|
|
10129
|
+
name: "show_panel_app",
|
|
10130
|
+
description: "Open a Panel App in the current chat UI as an immediate tool-driven preview. This tool is side-panel only. For inline Panel App display in a final reply, do not call this tool; output a Markdown nextclaw-inline fenced JSON block instead.",
|
|
10131
|
+
parameters: {
|
|
10132
|
+
type: "object",
|
|
10133
|
+
properties: {
|
|
10134
|
+
appId: {
|
|
10135
|
+
type: "string",
|
|
10136
|
+
description: "Installed Panel App id to show."
|
|
10137
|
+
},
|
|
10138
|
+
title: {
|
|
10139
|
+
type: "string",
|
|
10140
|
+
description: "Optional title for the shown content."
|
|
10141
|
+
},
|
|
10142
|
+
purpose: {
|
|
10143
|
+
type: "string",
|
|
10144
|
+
enum: PANEL_APP_PURPOSES,
|
|
10145
|
+
description: "Optional user intent."
|
|
10146
|
+
}
|
|
10147
|
+
},
|
|
10148
|
+
required: ["appId"],
|
|
10149
|
+
additionalProperties: false
|
|
10150
|
+
},
|
|
10151
|
+
normalize: normalizeShowPanelAppArgs
|
|
10152
|
+
}
|
|
10153
|
+
];
|
|
10154
|
+
function createShowContentTools(eventBus) {
|
|
10155
|
+
return SHOW_CONTENT_TOOL_SPECS.map((spec) => new ShowContentDisplayTool(eventBus, spec));
|
|
10156
|
+
}
|
|
9977
10157
|
//#endregion
|
|
9978
10158
|
//#region src/contributions/tool-provider/providers/show-content-tool.provider.ts
|
|
9979
10159
|
var ShowContentToolProvider = class {
|
|
9980
10160
|
constructor(eventBus) {
|
|
9981
10161
|
this.eventBus = eventBus;
|
|
9982
10162
|
}
|
|
9983
|
-
provide = () =>
|
|
10163
|
+
provide = () => createShowContentTools(this.eventBus);
|
|
9984
10164
|
};
|
|
9985
10165
|
//#endregion
|
|
9986
10166
|
//#region src/contributions/tool-provider/providers/structured-result-tool.provider.ts
|
|
@@ -10349,39 +10529,6 @@ async function dispatchChannelReplyRoute(params) {
|
|
|
10349
10529
|
await route.channel.consumeNcpReply(input);
|
|
10350
10530
|
}
|
|
10351
10531
|
//#endregion
|
|
10352
|
-
//#region src/utils/agent-run-metadata.utils.ts
|
|
10353
|
-
const LEGACY_RUN_METADATA_KEYS = [
|
|
10354
|
-
"account_id",
|
|
10355
|
-
"agent_id",
|
|
10356
|
-
"chat_id",
|
|
10357
|
-
"preferred_model",
|
|
10358
|
-
"project_root",
|
|
10359
|
-
"runtime",
|
|
10360
|
-
"sender_id",
|
|
10361
|
-
"session_key",
|
|
10362
|
-
"session_type"
|
|
10363
|
-
];
|
|
10364
|
-
function stripLegacyRunMetadata(metadata) {
|
|
10365
|
-
const nextMetadata = structuredClone(metadata);
|
|
10366
|
-
for (const key of LEGACY_RUN_METADATA_KEYS) delete nextMetadata[key];
|
|
10367
|
-
return nextMetadata;
|
|
10368
|
-
}
|
|
10369
|
-
function buildRunMetadata(params) {
|
|
10370
|
-
const { message, metadata, route } = params;
|
|
10371
|
-
return {
|
|
10372
|
-
...stripLegacyRunMetadata({
|
|
10373
|
-
...message.metadata ?? {},
|
|
10374
|
-
...metadata ?? {}
|
|
10375
|
-
}),
|
|
10376
|
-
channel: message.channel,
|
|
10377
|
-
chatId: message.chatId,
|
|
10378
|
-
accountId: route.accountId,
|
|
10379
|
-
agentId: route.agentId,
|
|
10380
|
-
sessionKey: route.sessionKey,
|
|
10381
|
-
senderId: message.senderId
|
|
10382
|
-
};
|
|
10383
|
-
}
|
|
10384
|
-
//#endregion
|
|
10385
10532
|
//#region src/features/ncp-dispatch/services/gateway-inbound-processor.service.ts
|
|
10386
10533
|
function formatUserFacingError(error, maxChars = 320) {
|
|
10387
10534
|
const normalized = (error instanceof Error ? error.message || error.name : String(error ?? "Unknown error")).replace(/\s+/g, " ").trim();
|