@nextclaw/kernel 0.5.4-beta.0 → 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.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 message = {
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 model = request.model ?? session.model ?? this.configManager.getDefaultModel();
692
- const agentId = request.agentId ?? session.agentId ?? this.agentManager.getDefaultAgentId();
693
- const spec = {
792
+ const { modelSource, spec } = resolveRunSpec({
793
+ agentManager: this.agentManager,
794
+ configManager: this.configManager,
795
+ request,
694
796
  runId: activeRun.runId,
695
- agentId,
696
- model,
697
- maxTokens: request.maxTokens ?? this.configManager.getModelMaxTokens(model),
698
- thinkingEffort: request.thinkingEffort ?? session.thinkingEffort ?? null,
699
- correlationId: request.correlationId
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
- type: NcpEventType.MessageCompleted,
725
- payload: {
726
- sessionId: event.payload.sessionId ?? session.sessionId,
727
- message,
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
- type: NcpEventType.RunError,
742
- payload: {
743
- error: error instanceof Error ? error.message : String(error),
744
- runId: spec.runId,
745
- sessionId: session.sessionId,
746
- correlationId: spec.correlationId
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$12(value) {
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$12(value)) return false;
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$12(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text.trim()).filter(Boolean);
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$11(value) {
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$11(message?.metadata?.checkpoint) ? message.metadata.checkpoint : null;
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$11(event.payload) ? event.payload : null)?.sessionId;
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$11(event.payload) ? event.payload : null;
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: event.payload.message.timestamp || 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$10(value) {
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$10(value)) return null;
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$9(value) || value.version !== 1 || !isRecord$9(value.grants)) return structuredClone(EMPTY_GRANTS$2);
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$9(callerValue) || !isRecord$9(callerValue.capabilities)) continue;
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$9(grantValue) && typeof grantValue.grantedAt === "string") capabilities[capability] = { grantedAt: grantValue.grantedAt };
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$9(value) {
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$8(value) || value.version !== 1 || !isRecord$8(value.grants)) return structuredClone(EMPTY_GRANTS$1);
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$8(grant) && typeof grant.grantedAt === "string") grants[appId] = { grantedAt: grant.grantedAt };
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$8(value) {
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$8(error) && error.code === "ENOENT";
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$7(input.schema)) throw new PanelAppError("PANEL_APP_AGENT_REQUEST_INVALID", "invalid generateObject request");
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$7(content) || content.ok !== false || !isRecord$7(content.error)) return;
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$7(value) {
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$6(parsed)) throw new Error("panel-app.json must contain an object.");
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$6(value) {
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$5(value) || value.version !== 1 || !isRecord$5(value.grants)) return structuredClone(EMPTY_GRANTS);
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$5(callerValue) || !isRecord$5(callerValue.actions)) continue;
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$5(actionValue) && typeof actionValue.grantedAt === "string" && isServiceActionRisk(actionValue.risk)) actions[actionId] = {
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$5(value) {
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$5(error) && error.code === "ENOENT";
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$4(parsed)) throw new Error("service-app.json must contain an object.");
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$4(value)) throw new Error("service app actions must be an object.");
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$4(action)) throw new Error(`service app action ${name} must be an object.`);
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$4(inputSchema)) throw new Error(`service app action ${name} inputSchema must be an object.`);
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$4(value) {
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$3(parsed) ? parsed : {};
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$3(value)) return;
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$3(value) {
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$11(parsed) || parsed._type !== "metadata" || !isRecord$11(parsed.metadata)) throw new Error(`invalid ncp agent session metadata sidecar: ${sessionId}`);
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$11(JSON.parse(serialized))) throw new Error("ncp agent session journal entry serialization produced a non-object entry");
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$11(event.payload)) return event;
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$11(parsed)) continue;
7350
+ if (!isRecord$10(parsed)) continue;
7235
7351
  if (parsed._type === "metadata") {
7236
- metadata = isRecord$11(parsed.metadata) ? structuredClone(parsed.metadata) : {};
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$11(parsed.event)) {
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
- "When you choose an inline card, call `show_content` with `type=\"panel_app\"` and `placement=\"inline\"` so the user can try it directly in the chat. Do not wait for the user to say \"inline\" or \"placement\" after you have already chosen the card form.",
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, choose `placement=\"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."
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: local openable files mentioned in user-visible replies must be clickable.\nAllowed form: 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).\nPath choice: use project-relative hrefs for files under the active/session project root, and absolute hrefs for local files outside it.\nForbidden forms: bare file names or paths, inline-code file names, bold-only file names, code-styled link labels, code blocks for file references, 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, remove the exact names and summarize instead."];
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$2(value) {
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$2(args)) throw new Error("session_search requires an object argument.");
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 SHOW_CONTENT_TOOL_NAME = "show_content";
9815
- function isRecord$1(value) {
9816
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
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
- const trimmed = value.trim();
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 readPurpose(value) {
9963
+ function readOptionalEnum(value, key, allowed) {
9833
9964
  const normalized = readOptionalString$1(value);
9834
9965
  if (!normalized) return;
9835
- if (normalized === "read" || normalized === "preview" || normalized === "edit" || normalized === "interact") return normalized;
9836
- throw new Error("purpose must be \"read\", \"preview\", \"edit\", or \"interact\".");
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, "payload.url");
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("payload.url must be a valid URL.");
9976
+ throw new Error("url must be a valid URL.");
9855
9977
  }
9856
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("payload.url must use http or https.");
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 normalizeShowContentArgs(args) {
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
- const type = readRequiredString(params.type, "type");
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(payload.path, "payload.path"),
9871
- line: readOptionalPositiveInteger(payload.line, "payload.line"),
9872
- column: readOptionalPositiveInteger(payload.column, "payload.column")
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
- title,
9876
- purpose,
9877
- placement
9999
+ ...readCommonRequestFields(params, FILE_PURPOSES)
9878
10000
  };
9879
- if (type === "url") return {
10001
+ }
10002
+ function normalizeShowUrlArgs(args) {
10003
+ const params = normalizeToolParams(args);
10004
+ return {
9880
10005
  target: {
9881
- type,
9882
- payload: { url: readUrl(payload.url) }
10006
+ type: "url",
10007
+ payload: { url: readUrl(params.url) }
9883
10008
  },
9884
- title,
9885
- purpose,
9886
- placement
10009
+ ...readCommonRequestFields(params, URL_PURPOSES)
9887
10010
  };
9888
- if (type === "panel_app") return {
10011
+ }
10012
+ function normalizeShowPanelAppArgs(args) {
10013
+ const params = normalizeToolParams(args);
10014
+ return {
9889
10015
  target: {
9890
- type,
9891
- payload: { appId: readRequiredString(payload.appId, "payload.appId") }
10016
+ type: "panel_app",
10017
+ payload: { appId: readRequiredString(params.appId, "appId") }
9892
10018
  },
9893
- title,
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: request.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
- constructor(eventBus) {
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 = normalizeShowContentArgs(args);
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 = () => [new ShowContentTool(this.eventBus)];
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();