@nextclaw/kernel 0.16.0 → 0.16.1

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
@@ -3,7 +3,7 @@ import { n as getUnsignedUpdateManifest, r as serializeUnsignedUpdateManifest, t
3
3
  import { createRequire } from "node:module";
4
4
  import { APP_NAME, AgentRouteResolver, BUILTIN_MAIN_AGENT_ID, CLEAR_THINKING_TOKENS, CONTEXT_COMPACTION_METADATA_KEY, ConfigSchema, ContextCompactionService, ContextWindowBudgetService, CronService, CronTool, DEFAULT_PANELS_DIR, DEFAULT_PROJECT_SKILLS_DIR_NAME, DEFAULT_SERVICE_APPS_DIR, DEFAULT_SESSION_SEARCH_LIMIT, DEFAULT_WORKSPACE_REPOSITORY_IDENTITY_RESOLVER, DIAGNOSTIC_CORRELATION_METADATA_KEY, DiagnosticRuntime, EditFileTool, ExecTool, ExtensionChannelAdapter, GatewayTool, InputBudgetPruner, LLMProvider, ListDirTool, LiteLLMProvider, LocalExecutionClaimService, MAX_SESSION_SEARCH_LIMIT, MemoryGetTool, MemorySearchTool, MemoryStore, MessageBus, MessageTool, ProviderModelDiscoveryService, ProviderRegistry, ReadFileTool, 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, estimateInputTokens, 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, resolveRuntimeCommandLaunch, resolveSecretRef, resolveSessionProjectContext, resolveSessionWorkspacePath, resolveThinkingLevel, safeFilename, sanitizeOutboundAssistantContent, saveConfig, summarizeSessionRequestTask, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
5
5
  import { NCP_AI_EXECUTION_METADATA_KEY, NCP_INTERNAL_VISIBILITY_METADATA_KEY, NCP_RUN_TRIGGER_METADATA_KEY, NcpEventType, OBSERVATION_EVENT_EXTENSION_TYPE, createUnavailableNcpAiExecutionMetadata, isHiddenNcpMessage, normalizeAssistantText, parseNcpRunTriggerInput, readNcpAiExecutionMetadata, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
6
- import { LocalAssetStore, buildAssetContentPath, buildOpenAiFunctionTool, isTextLikeAsset, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
6
+ import { LocalAssetStore, MODEL_ROUND_PART_OFFSETS, buildAssetContentPath, buildOpenAiFunctionTool, isTextLikeAsset, ncpMessageToOpenAiMessageGroups, ncpMessageToOpenAiMessages, validateToolArgs } from "@nextclaw/ncp-agent-runtime";
7
7
  import { CHAT_CONTINUATION_TARGET_MESSAGE_METADATA_KEY, CHAT_CONVERSATION_EXCERPT_TOKEN_KIND, CHAT_INLINE_TOKENS_METADATA_KEY, CHAT_INLINE_TOKENS_SCHEMA_VERSION, CHAT_PROJECT_TOKEN_KIND, CHAT_SESSION_MATERIALIZATION_METADATA_KEY, CHAT_SYSTEM_OBJECT_TOKEN_KIND, CHAT_UI_RESOURCE_TOKEN_KIND, CHAT_WORKSPACE_DIRECTORY_TOKEN_KIND, CHAT_WORKSPACE_EXCERPT_TOKEN_KIND, CHAT_WORKSPACE_FILE_TOKEN_KIND, Contribution as Contribution$1, EventBus, EventBus as EventBus$1, Ingress, Ingress as Ingress$1, PANEL_APP_INLINE_HOST_CONTRACT, PANEL_APP_SCROLL_RESTORATION_CONTRACT, SYSTEM_OBJECT_REFERENCE_DEFAULT_LIMIT, SYSTEM_OBJECT_REFERENCE_MAX_LIMIT, SYSTEM_OBJECT_TYPE_CRON_JOB, SYSTEM_OBJECT_TYPE_INBOX_DELIVERY, UI_CONTENT_PARAMS_HOST_CONTRACT, classifyDiagnosticError, createSystemObjectReferenceUri, createTypedKey, eventKeys, eventKeys as eventKeys$1, ingressKeys, ingressKeys as ingressKeys$1, isRuntimeDefaultModelValue, isSilentReplyNcpMessage, normalizeRuntimeModelSelectionMode, parseSystemObjectReferenceUri, readChatUiResourceReference, readInlineContentHeight, readSystemObjectResolvedReference, readUiContentParams } from "@nextclaw/shared";
8
8
  import { createHash, createHmac, randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
9
9
  import { catchError, filter, from, lastValueFrom, tap } from "rxjs";
@@ -187,6 +187,68 @@ var ContextCompactionJournalRecoveryService = class {
187
187
  };
188
188
  };
189
189
  //#endregion
190
+ //#region src/tools/structured-result.tools.ts
191
+ const STRUCTURED_RESULT_TOOL_NAME = "nextclaw_submit_result";
192
+ var StructuredResultSubmitTool = class {
193
+ name = STRUCTURED_RESULT_TOOL_NAME;
194
+ description = "Submit the structured object result for this request.";
195
+ constructor(contract) {
196
+ this.contract = contract;
197
+ }
198
+ get parameters() {
199
+ return this.contract.schema;
200
+ }
201
+ validateArgs = (args) => validateToolArgs(args, this.contract.schema);
202
+ execute = async (args) => args;
203
+ };
204
+ //#endregion
205
+ //#region src/tools/tool-schema.tools.ts
206
+ const TOOL_SCHEMA_NAME = "tool_schema";
207
+ const EAGER_TOOL_NAMES = new Set([
208
+ TOOL_SCHEMA_NAME,
209
+ STRUCTURED_RESULT_TOOL_NAME,
210
+ "node_repl",
211
+ "read_file",
212
+ "write_file",
213
+ "edit_file",
214
+ "list_dir",
215
+ "exec",
216
+ "web_search",
217
+ "web_fetch",
218
+ "view_image"
219
+ ]);
220
+ /** Model-facing disclosure only; execution continues to validate the original schema. */
221
+ function selectToolModelParameters(tool) {
222
+ return EAGER_TOOL_NAMES.has(tool.name) || !tool.parameters || JSON.stringify(tool.parameters).length <= 160 ? tool.parameters : { type: "object" };
223
+ }
224
+ var ToolSchemaTool = class {
225
+ name = TOOL_SCHEMA_NAME;
226
+ description = "Read full parameters for an available tool. Before first using a tool whose object schema has no properties field, query its exact name here, then call the original tool with the returned parameter shape.";
227
+ parameters = {
228
+ type: "object",
229
+ properties: { name: {
230
+ type: "string",
231
+ minLength: 1
232
+ } },
233
+ required: ["name"],
234
+ additionalProperties: false
235
+ };
236
+ constructor(readTools) {
237
+ this.readTools = readTools;
238
+ }
239
+ execute = async (args) => {
240
+ const name = args && typeof args === "object" && "name" in args ? args.name : null;
241
+ if (typeof name !== "string" || !name.trim()) throw new Error("Tool name is required.");
242
+ const tool = this.readTools().find((candidate) => candidate.name === name.trim());
243
+ if (!tool) throw new Error(`Tool is not in the current allowed catalog: ${name}`);
244
+ return structuredClone({
245
+ name: tool.name,
246
+ description: tool.description,
247
+ parameters: tool.parameters
248
+ });
249
+ };
250
+ };
251
+ //#endregion
190
252
  //#region src/utils/agent-model-input-budget.utils.ts
191
253
  function buildContextBlockInputMessages(contextBlocks = []) {
192
254
  const contextContent = contextBlocks.map((block) => block.trim()).filter(Boolean).join("\n\n");
@@ -196,10 +258,11 @@ function buildContextBlockInputMessages(contextBlocks = []) {
196
258
  }] : [];
197
259
  }
198
260
  function buildProviderTools(tools) {
261
+ const hasSchemaLookup = tools.some((tool) => tool.name === TOOL_SCHEMA_NAME);
199
262
  return tools.map((tool) => buildOpenAiFunctionTool({
200
263
  name: tool.name,
201
264
  description: tool.description,
202
- parameters: tool.parameters
265
+ parameters: hasSchemaLookup ? selectToolModelParameters(tool) : tool.parameters
203
266
  }));
204
267
  }
205
268
  function estimateToolInputTokens(tools) {
@@ -626,6 +689,7 @@ const NEXTCLAW_TIMELINE_KIND_METADATA_KEY = "nextclaw_timeline_kind";
626
689
  const CONTEXT_COMPACTION_TIMELINE_KIND = "context_compaction";
627
690
  const CONTEXT_COMPACTION_PROJECTION_METADATA_KEY = "nextclaw_context_projection";
628
691
  const CONTEXT_COMPACTION_PROJECTION_KIND = "compressed_context";
692
+ const CONTEXT_COMPACTION_PART_START = "nextclaw_compaction_part_start";
629
693
  const CONTEXT_COMPACTION_CONTINUATION_TEXT = "Continue the active run from the compressed working context. Do not repeat completed tool calls; proceed with the next required action.";
630
694
  const CONTEXT_COMPACTION_SYSTEM_PREAMBLE = ["Authoritative compressed prior conversation context for this session.", "Continue from this context and any following messages. Do not restart onboarding or treat missing profile fields as a new-session trigger unless the compressed context says onboarding is the active user task."].join("\n");
631
695
  function readCheckpointTimelineText(checkpoint) {
@@ -678,15 +742,24 @@ function buildMidRunContinuationMessage(params) {
678
742
  }]
679
743
  };
680
744
  }
745
+ function sliceMessageParts(message, start) {
746
+ if (!Number.isInteger(start) || start < 0 || start > message.parts.length) throw new Error("Invalid compacted message part boundary");
747
+ const projected = structuredClone(message);
748
+ projected.parts = projected.parts.slice(start);
749
+ projected.metadata = {
750
+ ...projected.metadata,
751
+ [CONTEXT_COMPACTION_PART_START]: start
752
+ };
753
+ const offsets = projected.metadata?.[MODEL_ROUND_PART_OFFSETS];
754
+ if (Array.isArray(offsets)) projected.metadata = {
755
+ ...projected.metadata,
756
+ [MODEL_ROUND_PART_OFFSETS]: offsets.filter((offset) => offset > start).map((offset) => offset - start)
757
+ };
758
+ return projected;
759
+ }
681
760
  function projectMessageAfterCheckpoint(message, checkpoint) {
682
761
  const coveredPartCount = checkpoint.continuationMessageCoveredPartCount;
683
- if (message.id === checkpoint.continuationMessageId && typeof coveredPartCount === "number" && Number.isInteger(coveredPartCount) && coveredPartCount >= 0) {
684
- const parts = message.parts.slice(coveredPartCount);
685
- return parts.length > 0 ? {
686
- ...structuredClone(message),
687
- parts: structuredClone(parts)
688
- } : null;
689
- }
762
+ if (message.id === checkpoint.continuationMessageId && typeof coveredPartCount === "number" && Number.isInteger(coveredPartCount) && coveredPartCount >= 0) return message.parts.slice(coveredPartCount).length > 0 ? sliceMessageParts(message, coveredPartCount) : null;
690
763
  return Date.parse(message.timestamp) > Date.parse(readCheckpointCoveredUntil(checkpoint)) ? structuredClone(message) : null;
691
764
  }
692
765
  function readCheckpointMessageIds(value) {
@@ -749,7 +822,7 @@ function buildContextCompactionModelProjection(params) {
749
822
  const preservedUserMessageIds = readCheckpointMessageIds(checkpoint.preservedUserMessageIds);
750
823
  const retainedMessageIds = readCheckpointMessageIds(checkpoint.retainedMessageIds);
751
824
  const preservedUserMessages = regularMessages.filter((message) => message.role === "user" && preservedUserMessageIds.has(message.id)).map((message) => projectPreservedUserMessage(message, checkpoint)).sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp));
752
- const retainedMessages = regularMessages.filter((message) => retainedMessageIds.has(message.id)).map((message) => structuredClone(message)).sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp));
825
+ const retainedMessages = regularMessages.filter((message) => retainedMessageIds.has(message.id)).map((message) => sliceMessageParts(message, checkpoint.retainedMessagePartStarts?.[message.id] ?? 0)).sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp));
753
826
  const projectedRegularMessages = regularMessages.filter((message) => !preservedUserMessageIds.has(message.id) && !retainedMessageIds.has(message.id)).map((message) => projectMessageAfterCheckpoint(message, checkpoint)).filter((message) => Boolean(message)).sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp));
754
827
  const stablePrefixMessages = [
755
828
  buildContextCompactionSummaryMessage({
@@ -809,11 +882,12 @@ function shouldRefreshContextWindowImmediately(event) {
809
882
  //#endregion
810
883
  //#region src/features/context-compaction/services/context-compaction-preflight.service.ts
811
884
  function toCompactionModelMessages(messages, assetStore) {
812
- return messages.flatMap((message) => ncpMessageToOpenAiMessages(message, { assetStore }).map((providerMessage) => ({
885
+ return messages.flatMap((message) => ncpMessageToOpenAiMessageGroups(message, { assetStore }).flatMap((group) => group.messages.map((providerMessage) => ({
813
886
  ...providerMessage,
814
887
  ncp_message_id: message.id,
888
+ ncp_part_start: group.partStart + Number(message.metadata?.["nextclaw_compaction_part_start"] ?? 0),
815
889
  timestamp: message.timestamp
816
- })));
890
+ }))));
817
891
  }
818
892
  function estimateCompactionProjectionOverhead(phase) {
819
893
  const summaryPlanOverhead = estimateInputTokens({
@@ -11097,21 +11171,6 @@ var PanelAppStateStore = class {
11097
11171
  isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
11098
11172
  };
11099
11173
  //#endregion
11100
- //#region src/tools/structured-result.tools.ts
11101
- const STRUCTURED_RESULT_TOOL_NAME = "nextclaw_submit_result";
11102
- var StructuredResultSubmitTool = class {
11103
- name = STRUCTURED_RESULT_TOOL_NAME;
11104
- description = "Submit the structured object result for this request.";
11105
- constructor(contract) {
11106
- this.contract = contract;
11107
- }
11108
- get parameters() {
11109
- return this.contract.schema;
11110
- }
11111
- validateArgs = (args) => validateToolArgs(args, this.contract.schema);
11112
- execute = async (args) => args;
11113
- };
11114
- //#endregion
11115
11174
  //#region src/utils/panel-app-agent.utils.ts
11116
11175
  const PANEL_APP_AGENT_DEFAULT_TIMEOUT_MS = 6e4;
11117
11176
  const PANEL_APP_AGENT_MAX_TIMEOUT_MS = 12e4;
@@ -12894,7 +12953,8 @@ var ToolProviderManager = class {
12894
12953
  };
12895
12954
  buildTools = async (request) => {
12896
12955
  const tools = [];
12897
- const seen = /* @__PURE__ */ new Set();
12956
+ tools.push(this.wrapTool(new ToolSchemaTool(() => tools), request));
12957
+ const seen = new Set([TOOL_SCHEMA_NAME]);
12898
12958
  for (const provider of [...this.providers]) for (const tool of await provider.provide(request)) {
12899
12959
  if (seen.has(tool.name)) continue;
12900
12960
  seen.add(tool.name);
@@ -13069,7 +13129,7 @@ function createReplayEvent(event, toolResultsByCallId) {
13069
13129
  const replayMessage = readMessageFromSummaryEvent$1(replayEvent);
13070
13130
  const legacyCompactionMessageId = readLegacyContextCompactionMessageId(replayMessage);
13071
13131
  if (replayMessage && legacyCompactionMessageId) replayMessage.id = legacyCompactionMessageId;
13072
- if (replayMessage?.role === "assistant" && (replayMessage.status === "pending" || replayMessage.status === "streaming")) replayMessage.status = "final";
13132
+ if (replayMessage && replayEvent.type === NcpEventType.MessageCompleted) replayMessage.status = "final";
13073
13133
  if (replayEvent.type === "session.snapshot.message" || replayEvent.type === NcpEventType.MessageCompleted) {
13074
13134
  replayEvent.payload.message = mergeReplayCompletedToolResults(replayEvent.payload.message, toolResultsByCallId);
13075
13135
  return {
@@ -13169,7 +13229,53 @@ function readMessageFromSummaryEvent$1(event) {
13169
13229
  if (event.type === NcpEventType.MessageSent || event.type === NcpEventType.MessageCompleted || event.type === "session.snapshot.message") return event.payload.message;
13170
13230
  }
13171
13231
  //#endregion
13232
+ //#region src/utils/ncp-agent-session-replay-tool-ownership.utils.ts
13233
+ function seedReplayToolOwners(messages) {
13234
+ const owners = /* @__PURE__ */ new Map();
13235
+ for (const message of messages) for (const part of message.parts) if (part.type === "tool-invocation" && part.toolCallId) owners.set(part.toolCallId, message.id);
13236
+ return owners;
13237
+ }
13238
+ function readReplayToolOwners(event) {
13239
+ const message = readMessageFromSummaryEvent$1(event);
13240
+ const owners = seedReplayToolOwners(message ? [message] : []);
13241
+ if (event.type === NcpEventType.MessageToolCallStart && event.payload.messageId) owners.set(event.payload.toolCallId, event.payload.messageId);
13242
+ return owners;
13243
+ }
13244
+ function needsReplayToolHistory(events, seeds) {
13245
+ const owners = seedReplayToolOwners(seeds);
13246
+ for (const event of events) {
13247
+ const toolCallId = readEventToolCallId(event);
13248
+ if (toolCallId && !readEventMessageId(event) && !owners.has(toolCallId)) return true;
13249
+ for (const [callId, messageId] of readReplayToolOwners(event)) owners.set(callId, messageId);
13250
+ }
13251
+ return false;
13252
+ }
13253
+ //#endregion
13172
13254
  //#region src/utils/ncp-agent-session-replay.utils.ts
13255
+ var ReplayContext = class {
13256
+ knownMessageIds;
13257
+ terminalMessageIds;
13258
+ toolResultsByCallId = /* @__PURE__ */ new Map();
13259
+ compactionRecovery = new ContextCompactionJournalRecoveryService();
13260
+ activeTailRunIds = /* @__PURE__ */ new Set();
13261
+ toolOwners;
13262
+ constructor(stateManager, seeds) {
13263
+ this.stateManager = stateManager;
13264
+ this.knownMessageIds = new Set(seeds.map((message) => message.id));
13265
+ this.terminalMessageIds = new Set(seeds.filter((message) => message.role === "assistant" && (message.status === "final" || message.status === "error")).map((message) => message.id));
13266
+ this.toolOwners = seedReplayToolOwners(seeds);
13267
+ this.compactionRecovery.seed(seeds);
13268
+ }
13269
+ recordEvent = (event, activeMessageId) => {
13270
+ for (const [callId, messageId] of readReplayToolOwners(event)) this.toolOwners.set(callId, messageId);
13271
+ const message = readMessageFromSummaryEvent$1(event);
13272
+ if (message?.role === "assistant" && (message.status === "final" || message.status === "error")) this.terminalMessageIds.add(message.id);
13273
+ if (event.type === NcpEventType.RunError || event.type === NcpEventType.RunFinished || event.type === NcpEventType.MessageAbort) {
13274
+ const messageId = readEventMessageId(event) ?? activeMessageId;
13275
+ if (messageId) this.terminalMessageIds.add(messageId);
13276
+ }
13277
+ };
13278
+ };
13173
13279
  async function replayNcpAgentSessionEvents(events, seedMessages = [], activeMessageId, allowUnknownStreamingBootstrap = true) {
13174
13280
  const context = await createReplayContext(seedMessages, activeMessageId);
13175
13281
  await replayJournalEvents(context, events, allowUnknownStreamingBootstrap);
@@ -13185,25 +13291,13 @@ async function createReplayContext(seedMessages, activeMessageId) {
13185
13291
  const activeMessage = activeMessageId ? seedMessages.find((message) => message.id === activeMessageId) : void 0;
13186
13292
  if (activeMessage) await stateManager.dispatch({
13187
13293
  occurredAt: activeMessage.timestamp,
13188
- type: NcpEventType.MessageReasoningStart,
13294
+ type: NcpEventType.MessageSent,
13189
13295
  payload: {
13190
13296
  sessionId: activeMessage.sessionId,
13191
- messageId: activeMessage.id
13297
+ message: activeMessage
13192
13298
  }
13193
13299
  });
13194
- const knownMessageIds = new Set(seedMessages.map((message) => message.id));
13195
- const terminalMessageIds = new Set(seedMessages.filter((message) => message.role === "assistant" && (message.status === "final" || message.status === "error")).map((message) => message.id));
13196
- const toolResultsByCallId = /* @__PURE__ */ new Map();
13197
- const compactionRecovery = new ContextCompactionJournalRecoveryService();
13198
- compactionRecovery.seed(seedMessages);
13199
- return {
13200
- stateManager,
13201
- knownMessageIds,
13202
- terminalMessageIds,
13203
- toolResultsByCallId,
13204
- compactionRecovery,
13205
- activeTailRunIds: /* @__PURE__ */ new Set()
13206
- };
13300
+ return new ReplayContext(stateManager, seedMessages);
13207
13301
  }
13208
13302
  async function replayJournalEvents(context, events, allowUnknownStreamingBootstrap) {
13209
13303
  const supersededSyntheticRecoveryIndexes = readSupersededSyntheticRecoveryIndexes(events);
@@ -13225,14 +13319,39 @@ function updateActiveTailRunIds(activeTailRunIds, event, eventIndex) {
13225
13319
  if (runId) activeTailRunIds.delete(runId);
13226
13320
  else activeTailRunIds.clear();
13227
13321
  }
13228
- async function replayJournalEvent(context, replayEvent, allowUnknownStreamingBootstrap) {
13229
- const { activeTailRunIds, compactionRecovery, knownMessageIds, terminalMessageIds } = context;
13322
+ async function replayJournalEvent(context, sourceEvent, allowUnknownStreamingBootstrap) {
13323
+ let replayEvent = sourceEvent;
13324
+ const { activeTailRunIds, compactionRecovery, knownMessageIds, terminalMessageIds, stateManager, toolOwners, recordEvent } = context;
13325
+ const summary = readMessageFromSummaryEvent$1(replayEvent);
13326
+ if (summary && (summary.status === "pending" || summary.status === "streaming") && terminalMessageIds.has(summary.id)) return;
13327
+ if (replayEvent.type === NcpEventType.MessageToolCallStart && !replayEvent.payload.messageId) {
13328
+ const activeId = stateManager.getSnapshot().streamingMessage?.id;
13329
+ if (!activeId) {
13330
+ console.warn(`[ncp-agent-session-journal] ignored tool start without a message: ${replayEvent.payload.toolCallId}`);
13331
+ return;
13332
+ }
13333
+ replayEvent = {
13334
+ ...replayEvent,
13335
+ payload: {
13336
+ ...replayEvent.payload,
13337
+ messageId: activeId
13338
+ }
13339
+ };
13340
+ }
13341
+ const toolCallId = readEventToolCallId(replayEvent);
13342
+ const toolOwner = toolCallId ? toolOwners.get(toolCallId) : void 0;
13343
+ if (toolCallId && replayEvent.type !== NcpEventType.MessageToolCallStart && !toolOwner) {
13344
+ console.warn(`[ncp-agent-session-journal] ignored unowned ${replayEvent.type}: ${toolCallId}`);
13345
+ return;
13346
+ }
13347
+ if (toolOwner && terminalMessageIds.has(toolOwner) && replayEvent.type !== NcpEventType.MessageToolCallResult) return;
13230
13348
  const streamingMessageId = readStreamingMessageId(replayEvent);
13231
13349
  if (streamingMessageId && terminalMessageIds.has(streamingMessageId)) return;
13232
13350
  if (streamingMessageId && !allowUnknownStreamingBootstrap && !knownMessageIds.has(streamingMessageId) && !isStreamingMessageCreationEvent(replayEvent) && activeTailRunIds.size === 0) return;
13233
13351
  compactionRecovery.track(replayEvent);
13352
+ const activeMessageId = stateManager.getSnapshot().streamingMessage?.id;
13234
13353
  await dispatchReplayEvent(context, replayEvent, streamingMessageId, allowUnknownStreamingBootstrap);
13235
- recordReplayTerminal(context, replayEvent);
13354
+ recordEvent(replayEvent, activeMessageId);
13236
13355
  }
13237
13356
  async function dispatchReplayEvent(context, replayEvent, streamingMessageId, allowUnknownStreamingBootstrap) {
13238
13357
  const { knownMessageIds, stateManager, toolResultsByCallId } = context;
@@ -13247,14 +13366,6 @@ async function dispatchReplayEvent(context, replayEvent, streamingMessageId, all
13247
13366
  await stateManager.dispatch(replayEvent);
13248
13367
  if (replayEvent.type === NcpEventType.MessageToolCallResult && replayEvent.payload.final !== false) toolResultsByCallId.set(replayEvent.payload.toolCallId, replayEvent.payload);
13249
13368
  }
13250
- function recordReplayTerminal(context, replayEvent) {
13251
- const replayMessage = readMessageFromSummaryEvent$1(replayEvent);
13252
- if (replayMessage?.role === "assistant" && (replayMessage.status === "final" || replayMessage.status === "error")) context.terminalMessageIds.add(replayMessage.id);
13253
- if (replayEvent.type === NcpEventType.RunError || replayEvent.type === NcpEventType.MessageAbort) {
13254
- const messageId = readEventMessageId(replayEvent);
13255
- if (messageId) context.terminalMessageIds.add(messageId);
13256
- }
13257
- }
13258
13369
  const NCP_AGENT_SESSION_JOURNAL_INDEX_FILE = ".ncp-agent-session-index.json";
13259
13370
  const NCP_AGENT_SESSION_SNAPSHOT_MESSAGE_EVENT_TYPE = "session.snapshot.message";
13260
13371
  const NCP_SESSION_REQUEST_ACCEPTED_EVENT_TYPE = "session.request.accepted";
@@ -15207,10 +15318,11 @@ var ServiceAppResidentEventInboxService = class {
15207
15318
  const existing = store.events.find((candidate) => candidate.eventId === input.eventId);
15208
15319
  if (existing) {
15209
15320
  event = existing;
15210
- return;
15321
+ return false;
15211
15322
  }
15212
15323
  const now = (/* @__PURE__ */ new Date()).toISOString();
15213
15324
  const streamKey = input.streamKey?.trim() || "default";
15325
+ if (store.events.some((candidate) => candidate.streamKey === streamKey && candidate.status === "dead-letter")) throw new ServiceAppError("SERVICE_APP_RESIDENT_EVENT_CONFLICT", `Resident stream ${streamKey} is blocked by a dead-letter event.`);
15214
15326
  const sequence = store.events.filter((candidate) => candidate.streamKey === streamKey).reduce((maximum, candidate) => Math.max(maximum, candidate.sequence), 0) + 1;
15215
15327
  event = {
15216
15328
  id: randomUUID(),
@@ -15251,24 +15363,35 @@ var ServiceAppResidentEventInboxService = class {
15251
15363
  const now = params.now ?? /* @__PURE__ */ new Date();
15252
15364
  const leaseMs = this.clampLease(params.leaseMs ?? DEFAULT_LEASE_MS);
15253
15365
  await this.mutate(scope, (store) => {
15254
- if (store.frozen) return;
15366
+ if (store.frozen) return false;
15255
15367
  const nowMs = now.getTime();
15368
+ let changed = false;
15256
15369
  for (const event of store.events) {
15257
15370
  if (event.status === "leased" && this.readTime(event.leaseExpiresAt) <= nowMs) {
15258
15371
  this.transition(event, "pending");
15259
15372
  event.leaseExpiresAt = void 0;
15373
+ changed = true;
15260
15374
  }
15261
15375
  if (event.status === "retry-wait" && this.readTime(event.nextAttemptAt) <= nowMs) {
15262
15376
  this.transition(event, "pending");
15263
15377
  event.nextAttemptAt = void 0;
15378
+ changed = true;
15264
15379
  }
15265
15380
  }
15266
- const candidate = store.events.filter((event) => event.status === "pending").filter((event) => !store.events.some((earlier) => earlier.streamKey === event.streamKey && earlier.sequence < event.sequence && earlier.status !== "acked")).sort((left, right) => left.receivedAt.localeCompare(right.receivedAt))[0];
15267
- if (!candidate) return;
15381
+ const streamHeads = /* @__PURE__ */ new Map();
15382
+ for (const event of store.events) {
15383
+ if (event.status === "acked") continue;
15384
+ const current = streamHeads.get(event.streamKey);
15385
+ if (!current || event.sequence < current.sequence) streamHeads.set(event.streamKey, event);
15386
+ }
15387
+ let candidate;
15388
+ for (const event of streamHeads.values()) if (event.status === "pending" && (!candidate || event.receivedAt < candidate.receivedAt)) candidate = event;
15389
+ if (!candidate) return changed;
15268
15390
  candidate.attempt += 1;
15269
15391
  this.transition(candidate, "leased");
15270
15392
  candidate.leaseExpiresAt = new Date(nowMs + leaseMs).toISOString();
15271
15393
  leased = candidate;
15394
+ return true;
15272
15395
  });
15273
15396
  return leased ? this.toLeased(leased) : void 0;
15274
15397
  };
@@ -15397,8 +15520,7 @@ var ServiceAppResidentEventInboxService = class {
15397
15520
  await this.ensureLoaded(scope);
15398
15521
  const key = scope.stateDirectory;
15399
15522
  const next = (this.mutationQueues.get(key) ?? Promise.resolve()).catch(() => void 0).then(async () => {
15400
- operation(this.requireStore(scope));
15401
- await this.save(scope);
15523
+ if (operation(this.requireStore(scope)) !== false) await this.save(scope);
15402
15524
  });
15403
15525
  this.mutationQueues.set(key, next);
15404
15526
  await next;
@@ -22332,7 +22454,7 @@ function deduplicateNcpAgentSessionTailMessages(messages) {
22332
22454
  function isNcpAgentSessionMessageProjectionMeta(value, sessionId) {
22333
22455
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
22334
22456
  const meta = value;
22335
- return meta.version === 7 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$6(meta.contextWindow));
22457
+ return meta.version === 8 && meta.sessionId === sessionId && Number.isSafeInteger(meta.total) && Number.isSafeInteger(meta.projectedJournalOffset) && Number.isSafeInteger(meta.dataBytes) && (meta.activeMessageId === null || typeof meta.activeMessageId === "string") && Array.isArray(meta.pendingCompactionMessageIds) && meta.pendingCompactionMessageIds.every((id) => typeof id === "string") && (meta.contextWindow === null || isRecord$6(meta.contextWindow));
22336
22458
  }
22337
22459
  function readActiveAssistantMessageId(messages) {
22338
22460
  for (let index = messages.length - 1; index >= 0; index -= 1) {
@@ -22364,6 +22486,34 @@ function readCompactionStatus(message) {
22364
22486
  return typeof checkpoint.status === "string" ? checkpoint.status : null;
22365
22487
  }
22366
22488
  //#endregion
22489
+ //#region src/stores/ncp-agent-session-message-projection-tail.store.ts
22490
+ async function readNcpAgentSessionProjectionTail(journalPath, meta, readMessage) {
22491
+ const file = await open(journalPath, "r");
22492
+ try {
22493
+ const { size } = await file.stat();
22494
+ const offset = meta.projectedJournalOffset;
22495
+ if (offset < 0 || offset >= size) return [];
22496
+ const buffer = Buffer.alloc(size - offset);
22497
+ const result = await file.read(buffer, 0, buffer.length, offset);
22498
+ const journal = parseNcpAgentSessionJournal(buffer.subarray(0, result.bytesRead).toString("utf-8"));
22499
+ const seedIds = new Set(meta.pendingCompactionMessageIds);
22500
+ if (meta.activeMessageId) seedIds.add(meta.activeMessageId);
22501
+ for (const event of journal.events) if (event.type === NcpEventType.RunFinished || event.type === NcpEventType.RunError || event.type === NcpEventType.RunMetadata || event.type === NcpEventType.MessageAbort) {
22502
+ const messageId = readEventMessageId(event);
22503
+ if (messageId) seedIds.add(messageId);
22504
+ }
22505
+ const seeds = (await Promise.all([...seedIds].map(readMessage))).filter((message) => Boolean(message));
22506
+ if (needsReplayToolHistory(journal.events, seeds)) {
22507
+ const prefix = Buffer.alloc(size);
22508
+ const full = await file.read(prefix, 0, size, 0);
22509
+ return await replayNcpAgentSessionEvents(parseNcpAgentSessionJournal(prefix.subarray(0, full.bytesRead).toString("utf-8")).events);
22510
+ }
22511
+ return await replayNcpAgentSessionEvents(journal.events, seeds, meta.activeMessageId, false);
22512
+ } finally {
22513
+ await file.close();
22514
+ }
22515
+ }
22516
+ //#endregion
22367
22517
  //#region src/stores/ncp-agent-session-message-projection-persistence.store.ts
22368
22518
  const PROJECTION_ROOT_DIRECTORY = ".message-projections";
22369
22519
  const RETRYABLE_META_RENAME_CODES = new Set([
@@ -22424,7 +22574,7 @@ var NcpAgentSessionMessageProjectionPersistenceStore = class {
22424
22574
  await dataFile.close();
22425
22575
  }
22426
22576
  const meta = {
22427
- version: 7,
22577
+ version: 8,
22428
22578
  sessionId,
22429
22579
  total: messages.length,
22430
22580
  projectedJournalOffset,
@@ -22546,23 +22696,7 @@ var NcpAgentSessionMessageProjectionPersistenceStore = class {
22546
22696
  contextWindow: meta.contextWindow ? structuredClone(meta.contextWindow) : null
22547
22697
  };
22548
22698
  };
22549
- readJournalTailMessages = async (sessionId, meta) => {
22550
- const file = await open(this.journalPath(sessionId), "r");
22551
- try {
22552
- const fileStat = await file.stat();
22553
- const offset = meta.projectedJournalOffset;
22554
- if (offset < 0 || offset > fileStat.size || offset === fileStat.size) return [];
22555
- const buffer = Buffer.alloc(fileStat.size - offset);
22556
- const result = await file.read(buffer, 0, buffer.length, offset);
22557
- const journal = parseNcpAgentSessionJournal(buffer.subarray(0, result.bytesRead).toString("utf-8"));
22558
- const seedMessageIds = new Set(meta.pendingCompactionMessageIds);
22559
- if (meta.activeMessageId) seedMessageIds.add(meta.activeMessageId);
22560
- const seedMessages = (await Promise.all([...seedMessageIds].map((messageId) => this.readMessageById(sessionId, meta, messageId)))).filter((message) => Boolean(message));
22561
- return await replayNcpAgentSessionEvents(journal.events, seedMessages, meta.activeMessageId, false);
22562
- } finally {
22563
- await file.close();
22564
- }
22565
- };
22699
+ readJournalTailMessages = async (sessionId, meta) => readNcpAgentSessionProjectionTail(this.journalPath(sessionId), meta, (messageId) => this.readMessageById(sessionId, meta, messageId));
22566
22700
  delete = async (sessionId) => {
22567
22701
  this.messageOrdinals.delete(sessionId);
22568
22702
  await rm(this.projectionPath(sessionId), {
@@ -22992,6 +23126,7 @@ function summaryToRow(summary, deletedAt = null) {
22992
23126
  };
22993
23127
  }
22994
23128
  function rowToSummary(row) {
23129
+ const metadata = JSON.parse(row.metadata_json);
22995
23130
  return {
22996
23131
  sessionId: row.session_id,
22997
23132
  ...row.peer_id ? { peerId: row.peer_id } : {},
@@ -23000,7 +23135,8 @@ function rowToSummary(row) {
23000
23135
  ...row.created_at ? { createdAt: row.created_at } : {},
23001
23136
  updatedAt: row.updated_at,
23002
23137
  ...row.last_message_at ? { lastMessageAt: row.last_message_at } : {},
23003
- status: row.status
23138
+ status: row.status,
23139
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
23004
23140
  };
23005
23141
  }
23006
23142
  var NcpAgentSessionSummaryIndexStore = class {
@@ -23218,19 +23354,6 @@ var NcpAgentSessionSummaryIndexStore = class {
23218
23354
  };
23219
23355
  //#endregion
23220
23356
  //#region src/stores/ncp-agent-session-summary-read.store.ts
23221
- const METADATA_READ_CONCURRENCY = 2;
23222
- async function mapWithConcurrency(values, project) {
23223
- const results = new Array(values.length);
23224
- let nextIndex = 0;
23225
- const workers = Array.from({ length: Math.min(METADATA_READ_CONCURRENCY, values.length) }, async () => {
23226
- while (nextIndex < values.length) {
23227
- const index = nextIndex++;
23228
- results[index] = await project(values[index]);
23229
- }
23230
- });
23231
- await Promise.all(workers);
23232
- return results;
23233
- }
23234
23357
  var NcpAgentSessionSummaryReadStore = class {
23235
23358
  constructor(options) {
23236
23359
  this.options = options;
@@ -23238,7 +23361,7 @@ var NcpAgentSessionSummaryReadStore = class {
23238
23361
  list = async (limit) => {
23239
23362
  const normalizedLimit = limit === void 0 || limit === Number.POSITIVE_INFINITY ? void 0 : Math.max(0, Math.trunc(limit));
23240
23363
  const summaries = await this.options.summaryIndex.list(normalizedLimit);
23241
- return await mapWithConcurrency(normalizedLimit === void 0 ? summaries : summaries.slice(0, normalizedLimit), this.withMetadata);
23364
+ return normalizedLimit === void 0 ? summaries : summaries.slice(0, normalizedLimit);
23242
23365
  };
23243
23366
  listPage = async (options) => {
23244
23367
  const page = Math.max(1, Math.trunc(options.page));
@@ -23249,13 +23372,13 @@ var NcpAgentSessionSummaryReadStore = class {
23249
23372
  ...options.query?.trim() ? { query: options.query.trim() } : {}
23250
23373
  }), this.options.summaryIndex.count(options.query)]);
23251
23374
  return {
23252
- sessions: await mapWithConcurrency(summaries, this.withMetadata),
23375
+ sessions: summaries,
23253
23376
  total
23254
23377
  };
23255
23378
  };
23256
23379
  get = async (sessionId) => {
23257
23380
  const indexed = await this.options.summaryIndex.get(sessionId);
23258
- if (indexed) return await this.withMetadata(indexed);
23381
+ if (indexed) return indexed;
23259
23382
  const messageCount = await this.options.readProjectedMessageCount(sessionId);
23260
23383
  if (messageCount === null) return null;
23261
23384
  const updatedAt = await this.options.readJournalModifiedAt(sessionId);
@@ -23276,21 +23399,6 @@ var NcpAgentSessionSummaryReadStore = class {
23276
23399
  messageCount
23277
23400
  };
23278
23401
  };
23279
- withMetadata = async (summary) => {
23280
- const snapshot = await this.options.readMetadata(summary.sessionId, {
23281
- ...summary.agentId ? { agentId: summary.agentId } : {},
23282
- createdAt: summary.createdAt ?? summary.updatedAt,
23283
- updatedAt: summary.updatedAt,
23284
- metadata: {}
23285
- });
23286
- const peerId = summary.peerId ?? readNcpAgentSessionPeerId(snapshot.metadata);
23287
- return {
23288
- ...summary,
23289
- peerId: peerId ?? void 0,
23290
- ...!summary.agentId && snapshot.agentId ? { agentId: snapshot.agentId } : {},
23291
- ...Object.keys(snapshot.metadata).length > 0 ? { metadata: snapshot.metadata } : {}
23292
- };
23293
- };
23294
23402
  };
23295
23403
  //#endregion
23296
23404
  //#region src/stores/ncp-agent-session-journal.store.ts
@@ -24884,10 +24992,9 @@ const createSelfUpdateContextProvider = () => staticBlock([
24884
24992
  `## ${APP_NAME} Self-Update`,
24885
24993
  "Get Updates (self-update) is ONLY allowed when the user explicitly asks for it.",
24886
24994
  "Do not run config.apply or update.run unless the user explicitly requests an update or config change; if it's not explicit, ask first.",
24887
- "Actions: config.get, config.schema, config.apply (validate + write full config), config.patch (merge config), update.run (update the runtime and relaunch the service).",
24888
- "When patching config, copy enum values exactly from config.schema; never invent new variants.",
24889
- "session.dmScope legal values are exactly: main | per-peer | per-channel-peer | per-account-channel-peer.",
24890
- "If an enum/path is uncertain, stop and call config.schema first; do not guess.",
24995
+ "For configuration management, prefer object-level CLI commands from the self-management guide: nextclaw providers, models, search, agents, mcp and other covered commands. Do not read or edit config files, or use generic config set/unset or gateway config actions, for tasks covered by these commands.",
24996
+ "Gateway config.get/config.schema/config.apply/config.patch remain transitional tools for documented CLI gaps or explicit manual recovery; they are not the recommended path. update.run updates the runtime and relaunches the service.",
24997
+ "Only when a transitional config write is needed, read config.get and config.schema first, copy legal values exactly, use the minimal config.patch, then verify with config.get. Do not guess paths or enum values.",
24891
24998
  `If a config change requires restart, tell the user to run \`${APP_NAME.toLowerCase()} restart\` in an external terminal. Do not run it from the active agent session.`
24892
24999
  ]);
24893
25000
  const createReplyTagsContextProvider = () => staticBlock([
@@ -24901,19 +25008,12 @@ const createReplyTagsContextProvider = () => staticBlock([
24901
25008
  ]);
24902
25009
  const createMessagingContextProvider = () => staticBlock([
24903
25010
  "## Messaging",
24904
- "- Answer needed in the current conversation → reply normally; it automatically routes to the source channel.",
24905
- "- Durable reading material with no explicit external destination → use `deliver_to_inbox`. Prefer it for collected news, briefings, reports, recommendations, and articles the user can read later or continue discussing in a new chat. Wording such as \"send it to me\" alone does not name a chat channel.",
24906
- "- Another conversation or an explicitly named channel use `message(action=send)`; use `sessions_list` first when you need to recover an existing route without guessing.",
24907
- "- Sub-agent orchestration use subagents(action=list|steer|kill)",
24908
- "- `[System Message] ...` blocks are internal context and are not user-visible by default.",
24909
- "- If a `[System Message]` reports completed cron/subagent work and asks for a user update, rewrite it in your normal assistant voice and send that update (do not forward raw system text or default to <noreply/>).",
24910
- `- Never use exec/curl for provider messaging; ${APP_NAME} handles all routing internally.`,
24911
- "",
24912
- "### message tool",
24913
- "- Use `message` for sends to an explicit conversation/channel route and for channel actions (polls, reactions, etc.); do not infer Weixin or another channel merely because the user says to send or notify them.",
24914
- "- For `action=send`, include `message` plus an explicit `to/chatId` whenever the destination is another channel or another conversation.",
24915
- "- Omitting `to/chatId` only replies to the current conversation; if you set `channel` to a different channel than the current session, `to/chatId` is required.",
24916
- "- If you use `message` (`action=send`) to deliver your user-visible reply, respond with ONLY <noreply/> (avoid duplicate replies)."
25011
+ "- Reply normally in the current conversation; routing to its source channel is automatic.",
25012
+ "- Use `deliver_to_inbox` for durable reading material (news, briefings, reports, recommendations, articles) unless an external destination is explicit. \"Send it to me\" alone does not name a channel.",
25013
+ "- Use `message(action=send)` for an explicit conversation/channel destination, and `message` for channel actions such as polls/reactions. Use `sessions_list` to recover an existing route when needed; never guess a route or infer a channel from availability.",
25014
+ "- For another conversation or channel, supply `to/chatId`. Omitting it replies only to the current conversation; changing `channel` requires an explicit destination. After delivering the visible reply via `message`, return ONLY <noreply/> to prevent duplicates.",
25015
+ "- Sub-agent control uses subagents(action=list|steer|kill). Internal `[System Message]` blocks are not user-visible by default. When one requests a cron/subagent completion update, rewrite it in your own voice; do not forward raw system text or use <noreply/> instead.",
25016
+ "- Never use exec/curl for provider messaging; NextClaw handles routing."
24917
25017
  ]);
24918
25018
  const createMemoryRecallContextProvider = () => staticBlock([
24919
25019
  "## Memory Recall",
@@ -24960,13 +25060,8 @@ const createSessionOrchestrationContextProvider = () => staticBlock([
24960
25060
  "## Session Orchestration",
24961
25061
  "- Only top-level sessions can create new sessions. Child sessions must complete their delegated task directly and return further delegation needs to the parent session.",
24962
25062
  "- Before passing a non-default `runtime` to `sessions_spawn` or agent creation/update flows, inspect the installed runtime kinds with `nextclaw agents runtimes --json`.",
24963
- "- `sessions_spawn` is the unified session-creation tool. Omit `scope` or use `scope=\"standalone\"` for a regular session, and use `scope=\"child\"` when the new session should be a child session of the current flow.",
24964
- "- `sessions_spawn` starts the task immediately by default and returns a running handle without waiting. Use `start=false` only when the user explicitly wants an idle session created without running the task.",
24965
- "- `wait=\"none\"` is the default and lets this session continue immediately; use `wait=\"final_reply\"` only when the current tool call must block for the target result.",
24966
- "- `notify=\"final_reply\"` is the default and queues a hidden completion follow-up for this session; use `notify=\"none\"` when the target should finish independently without waking this session.",
24967
- "- Use `sessions_request` to send one task to an existing session, including a session that was just created by `sessions_spawn` or a previously created child session.",
24968
- "- `sessions_request.target` must be an object shaped like `{ \"session_id\": \"<target-session-id>\" }`. Do not pass a bare string.",
24969
- "- `sessions_request` uses the same independent `wait` and `notify` policies; neither option controls whether the target request starts."
25063
+ "- Use `sessions_spawn` to create a session; use `sessions_request` to send a task to an existing session. Creation starts work immediately unless the user explicitly wants an idle session (`start=false`).",
25064
+ "- `wait` controls blocking; `notify` independently controls completion delivery. Neither controls whether a request starts. Use the tool schemas for parameter shapes, enum values and defaults."
24970
25065
  ]);
24971
25066
  //#endregion
24972
25067
  //#region src/contributions/context-provider/providers/project-context.provider.ts
@@ -25018,19 +25113,12 @@ var ReplyFormatContextProvider = class {
25018
25113
  "Final reply: the UI collapses activity through the last tool call. After that call, always write a concise, self-contained final response covering outcome, caveats, links, and next useful action without relying on prior narration or raw output.",
25019
25114
  "Presentation: choose the smallest medium that materially reduces effort. Keep simple facts, short explanations, one or two steps, and simple edits in prose. Use compact Markdown tables for exact mappings/comparisons; focused Mermaid for relationships or flow; charts only for numeric patterns; images for appearance/spatial concepts; inline HTML only when spatial layout or interaction is clearer. Never invent facts, scores, thresholds, rankings, or labels.",
25020
25115
  "Visualization gate: for an explicit visualization/chart/diagram/timeline/dashboard/status-result view, the FIRST tool call MUST be `read_file` for the built-in `visualize-output` SKILL.md; also read it before a strong implicit visual candidate. Data fidelity: use only supported facts and mathematics, calculate derived values with a tool, verify the artifact, apply no unsupplied threshold or qualitative label, and add no unsupported cause, recommendation, benchmark, target, forecast, or effect. For summary-only requests, stop at what the data shows.",
25021
- "Mermaid: use a focused fenced `mermaid` block and quote punctuated labels; ASCII/code-fence substitutes do not count. Prefer it for 3+ ordered, dependent, owned, or feedback-linked nodes unless prose is unambiguous. A table already counts as visualization; escalate only when requested or a graphic reveals a material pattern.",
25022
- `Visualization assets: put conversation-only files in \`${resolveVisualizationAssetDirectory(request)}\`; create it as needed, use absolute inline \`file\` paths, and never use \`/tmp\`, temporary directories, the project, or cwd. Use the project only for project-owned deliverables.`,
25023
- "Inline visualization: create and verify self-contained HTML, then use a `nextclaw-inline` `file` target, never a local/invented URL or duplicate table/list. Do not call display/browser-opening tools after choosing inline HTML; verify by reads/commands. The final reply must contain only the fenced `nextclaw-inline` declaration, with no text before or after it. Add no unrequested derived time metrics. Use `nextclaw-app-creator` for reusable apps/workflows.",
25116
+ "Before any inline display (including an existing Panel App), read the built-in `visualize-output` SKILL.md for the complete display contract. This does not authorize creating or changing an app. For app creation or changes, use `nextclaw-app-creator`. If required rules are no longer in context, read them again before acting.",
25024
25117
  "Markdown: prefer short paragraphs and add headings, lists, tables, blockquotes, or code only when they improve scanning. Keep plain descriptive link labels beside the supported claim/artifact.",
25025
25118
  "File links: every concrete local file/directory named in the final reply must be a Markdown link with a plain label. Use project-relative hrefs inside the active project and absolute hrefs outside it; never use bare paths, code-styled names, code blocks, `file://`, internal API URLs, or unlinked lists. Link even if unverified. Files open as source; `?viewer=source` forces source and `?viewer=rendered` renders HTML.",
25026
25119
  "Local images: display with `![label](project-relative-or-absolute-path)`; never invent internal URLs. `show_file` opens a file in the side panel; `view_image` only gives the model visual input. Make each named resource clickable, rendered, intentionally inline, or omit its exact name and summarize.",
25027
- "Display choice: inline only for compact cards/short interactions. Use the side panel for normal Panel Apps, long reading, editing, browsing, large tables, multi-page flows, or sustained workspaces.",
25028
- "Inline display: for a non-clickable inline placeholder, output a fenced `nextclaw-inline` JSON block:",
25029
- "```nextclaw-inline\n{\"target\":{\"type\":\"panel_app\",\"payload\":{\"appId\":\"timer\"}},\"title\":\"Timer\"}\n```",
25030
- "Inline targets: `panel_app`, `json`, `file`, and `url`. Add absolute `payload.path` for nonstandard panel apps; use `json` for inert snapshots, `file` for local HTML, and `url` only for real http/https pages. `file`/`url` are non-clickable; link when clicking is intended.",
25031
- "Params: panel apps and rendered HTML files may carry immutable initial JSON at `payload.params`, read synchronously from `window.nextclaw.params`; do not rename or nest it.",
25032
25120
  "Inline declarations are Markdown-only and display-only, with no actions or tool calls. Never call `show_panel_app` for inline display. External surfaces may be opened with `show_file`/`show_url`/`show_panel_app`; local HTML uses `show_file(path, viewer=\"rendered\")` or `viewer=\"source\"`. Do not convert HTML to a Panel App just to preview it.",
25033
- "Panel Cards are card-first, normally landscape, and one-column only when narrow. Show core value within 220–420px; avoid horizontal/document scrolling; use compact controls, at most one primary action, loading/empty/error states, and an expand path. Larger UI belongs in the side panel. Honor `nextclawDisplayMode=card` and `nextclawPlacement=inline`."
25121
+ `Visualization assets: put conversation-only files in \`${resolveVisualizationAssetDirectory(request)}\`; create it as needed, use absolute inline \`file\` paths, and never use \`/tmp\`, temporary directories, the project, or cwd. Use the project only for project-owned deliverables.`
25034
25122
  ].join("\n")];
25035
25123
  };
25036
25124
  //#endregion
@@ -25052,8 +25140,9 @@ function renderSkillSourcesSection(params) {
25052
25140
  "- Each catalog group gives its exact root; read a skill at `Root/<name>/SKILL.md`. Project `AGENTS.md` is separate bootstrap context."
25053
25141
  ].join("\n");
25054
25142
  }
25055
- function renderAvailableSkillsSection(skills) {
25056
- const summary = skills.buildSkillsSummary();
25143
+ function renderAvailableSkillsSection(skills, alwaysOnSkills) {
25144
+ const activeRefs = new Set(alwaysOnSkills);
25145
+ const summary = skills.buildSkillsManifest(skills.listSkills().filter((skill) => !activeRefs.has(skill.ref)).map((skill) => skill.ref));
25057
25146
  if (!summary) return "";
25058
25147
  return [
25059
25148
  "## Skills",
@@ -25090,7 +25179,7 @@ var SkillsContextProvider = class {
25090
25179
  const alwaysOnSection = renderAlwaysOnSkillsSection(skills, alwaysOnSkills);
25091
25180
  if (alwaysOnSection) blocks.push(alwaysOnSection);
25092
25181
  }
25093
- const availableSkillsSection = renderAvailableSkillsSection(skills);
25182
+ const availableSkillsSection = renderAvailableSkillsSection(skills, alwaysOnSkills);
25094
25183
  if (availableSkillsSection) blocks.push(availableSkillsSection);
25095
25184
  blocks.push(renderSkillLearningSection());
25096
25185
  return blocks;
@@ -25593,31 +25682,31 @@ var ContextProviderContribution = class extends Contribution$1 {
25593
25682
  const context = new ContextProviderRunContextService(this.kernel);
25594
25683
  for (const provider of [
25595
25684
  createAssistantIdentityContextProvider(),
25596
- new ToolingContextProvider(context),
25597
25685
  createToolCallStyleContextProvider(),
25598
25686
  createChatComposerTokensContextProvider(),
25599
25687
  createSafetyContextProvider(),
25600
25688
  createCliQuickReferenceContextProvider(),
25601
25689
  createSelfUpdateContextProvider(),
25602
- new WorkspaceContextProvider(context),
25603
25690
  createReplyTagsContextProvider(),
25604
25691
  createMessagingContextProvider(),
25605
25692
  createMemoryRecallContextProvider(),
25606
25693
  createSilentRepliesContextProvider(),
25607
25694
  createRuntimeContextProvider(),
25608
25695
  createSelfManagementContextProvider(),
25696
+ createSessionOrchestrationContextProvider(),
25697
+ new ReplyFormatContextProvider(),
25698
+ new ToolingContextProvider(context),
25699
+ new WorkspaceContextProvider(context),
25609
25700
  new ProjectContextProvider(context),
25610
25701
  new ConversationExcerptContextProvider(),
25611
25702
  new WorkspaceReferenceContextProvider(context, this.kernel.projectManager),
25612
25703
  new AgentBootstrapContextProvider(context),
25613
25704
  new WorkspaceMemoryContextProvider(context),
25614
25705
  new SkillsContextProvider(context),
25615
- createSessionOrchestrationContextProvider(),
25616
25706
  new ExecutionPolicyContextProvider(context),
25617
25707
  new SystemObjectReferenceContextProvider(this.kernel.assetStore),
25618
25708
  new UiResourceReferenceContextProvider(),
25619
- new CurrentSessionContextProvider(context),
25620
- new ReplyFormatContextProvider()
25709
+ new CurrentSessionContextProvider(context)
25621
25710
  ]) this.effect(() => this.kernel.contextProviderManager.register(provider));
25622
25711
  };
25623
25712
  };
@@ -26817,7 +26906,6 @@ var SessionToolProvider = class {
26817
26906
  });
26818
26907
  tools.unshift(sessionsSpawnTool);
26819
26908
  }
26820
- if (!this.sessionSearch.isReady()) return tools;
26821
26909
  tools.push(new SessionSearchTool({ search: this.sessionSearch.search }, { currentSessionId: sessionId }));
26822
26910
  return tools;
26823
26911
  };
@@ -29140,7 +29228,7 @@ var NextclawKernelFacade = class {
29140
29228
  registerProvider: (plugin) => this.kernel.llmProviders.registerProviderPlugin(plugin),
29141
29229
  listProviders: () => this.kernel.llmProviders.listProviderSpecs(),
29142
29230
  chat: this.kernel.llmProviders.chat,
29143
- chatStream: this.kernel.llmProviders.chatStream
29231
+ chatStream: this.kernel.llmProviders.chatStream.bind(this.kernel.llmProviders)
29144
29232
  };
29145
29233
  this.runtimes = {
29146
29234
  registerProvider: (provider) => this.kernel.agentRuntimeManager.registerProvider(provider, { resolveAssetContentPath: this.kernel.assetStore.resolveContentPath }),
@@ -29679,6 +29767,6 @@ function resolveLegacyEventType(message) {
29679
29767
  return `message.${role || "other"}`;
29680
29768
  }
29681
29769
  //#endregion
29682
- export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT_FINGERPRINT, PORTABLE_RUNTIME_ACCEPTANCE_LOCALES, PORTABLE_RUNTIME_ACCEPTANCE_PLATFORMS, PORTABLE_RUNTIME_ACCEPTANCE_PRESENTATION, PORTABLE_RUNTIME_ACCEPTANCE_REFERENCE_APP_ID, PROJECT_TEMPLATE_IDS, PROJECT_WORK_ATTENTION_VALUES, PROJECT_WORK_STATE_CATEGORIES, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PortableRuntimeAcceptanceIdentityService, PortableRuntimeAcceptanceManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProjectMaterialService, ProjectWorkError, ProjectWorkManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppAiCapabilityService, ServiceAppError, ServiceAppJobJournalService, ServiceAppManager, ServiceAppResidentEventInboxService, ServiceAppRuntimeService, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, VerificationRecordService, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, buildSessionRequestCompletionMessage, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAgentRuntimeSessionRequestSourceNotifier, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createServiceAppAgentSlotGrantRequest, createServiceAppModelSlotGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, evaluatePortableRuntimeAcceptance, evaluatePortableRuntimeAcceptanceArtifact, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isProjectWorkError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, normalizePortableRuntimeEnvironment, parseObservationDuration, parsePortableRuntimeAcceptanceEvidenceArtifact, parseServiceAppManifest, parseSkillFrontmatter, presentPortableRuntimeAcceptanceDefinition, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, readServiceAppSlotTarget, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolvePortableRuntimeAcceptanceLocale, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
29770
+ export { AUTOMATIC_UPDATE_CHECK_INTERVAL_MS, AccessManager, AgentManager, AgentRunClient, AppDataError, AppDataManager, AppPackageError, AppPackageManager, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_CONTINUATION_TEXT, CONTEXT_COMPACTION_PART_START, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_SYSTEM_PREAMBLE, CONTEXT_COMPACTION_TIMELINE_KIND, CapabilityGrantLegacyMigrationService, CapabilityGrantManager, CapabilityGrantStore, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionJournalRecoveryService, ContextCompactionPreflightService, Contribution, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, DESKTOP_HOST_ACCESS, DESKTOP_HOST_PROTOCOL_VERSION, DesktopHostCapabilityManager, DesktopNodeReplService, DesktopSessionStateService, EventBus, ExtensionManager, FeatureControlsService, GatewayInboundProcessor, InboxDeliveryError, InboxDeliveryManager, Ingress, LlmProviderManager, LlmUsageManager, LlmUsageStore, MAX_INBOX_DELIVERY_CONTENT_LENGTH, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawHarness, NextclawHarnessError, NextclawKernel, ObservationManager, PANEL_APP_AGENT_CAPABILITIES, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT, PORTABLE_RUNTIME_ACCEPTANCE_CONTRACT_FINGERPRINT, PORTABLE_RUNTIME_ACCEPTANCE_LOCALES, PORTABLE_RUNTIME_ACCEPTANCE_PLATFORMS, PORTABLE_RUNTIME_ACCEPTANCE_PRESENTATION, PORTABLE_RUNTIME_ACCEPTANCE_REFERENCE_APP_ID, PROJECT_TEMPLATE_IDS, PROJECT_WORK_ATTENTION_VALUES, PROJECT_WORK_STATE_CATEGORIES, PROVIDER_MODEL_CATALOG_REFRESH_INTERVAL_MS, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PortableRuntimeAcceptanceIdentityService, PortableRuntimeAcceptanceManager, PreferenceError, PreferenceManager, ProjectError, ProjectManager, ProjectMaterialService, ProjectWorkError, ProjectWorkManager, ProviderManagerNcpLLMApi, ProviderModelCatalogManager, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppAiCapabilityService, ServiceAppError, ServiceAppJobJournalService, ServiceAppManager, ServiceAppResidentEventInboxService, ServiceAppRuntimeService, SessionContextCompactionError, SessionContextCompactionManager, SessionManager, SessionMessageCursorError, SessionRequestManager, SessionSettingsError, SkillManager, SystemObjectReferenceError, SystemObjectReferenceManager, UnavailableDesktopHost, UpdateManifestReader, VerificationRecordService, assertObservationJsonValue, assertObservationPredicate, buildAgentRunSendPayload, buildContextCompactionModelProjection, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildObservationEventModelMessage, buildServiceActionId, buildSessionRequestCompletionMessage, capabilityGrantCovers, createAgentRuntimeSessionRequestDispatcher, createAgentRuntimeSessionRequestSourceNotifier, createAssetTools, createCapabilityDeclarationFingerprint, createContextCompactionMessageId, createContextWindowSignature, createCronJobSystemObjectProvider, createDesktopHostError, createInboxDeliverySystemObjectProvider, createLlmUsageRecord, createPanelAppAgentGrantRequest, createPanelAppClientGrantRequest, createServiceActionGrantRequest, createServiceAppAgentSlotGrantRequest, createServiceAppModelSlotGrantRequest, createTypedKey, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, dispatchPromptOverNcpResult, evaluateObservationEventAdmission, evaluatePortableRuntimeAcceptance, evaluatePortableRuntimeAcceptanceArtifact, eventKeys, getAutomaticUpdateCheckDelay, getCapabilityGrantKey, getServiceActionName, getServiceAppManifestPath, getUiContentParamsBootstrapScript, getUnsignedUpdateManifest, hasLlmUsageTelemetry, ingressKeys, injectUiContentParamsBootstrap, isAppDataError, isAppPackageError, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isInboxDeliveryError, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isProjectError, isProjectWorkError, isReplyCapableChannel, isServiceAppError, isSessionContextCompactionError, isSessionMessageCursorError, isSessionSettingsError, isSystemObjectReferenceError, listExtensionChannelIds, listServiceAppManifestActions, matchesCapabilityGrantFilter, matchesObservationPredicate, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeCapabilityGrantRequest, normalizeLlmUsageModel, normalizeOptionalString, normalizePortableRuntimeEnvironment, parseObservationDuration, parsePortableRuntimeAcceptanceEvidenceArtifact, parseServiceAppManifest, parseSkillFrontmatter, presentPortableRuntimeAcceptanceDefinition, readContextCompactionCheckpoint, readContextWindowEventSessionId, readJsonPointer, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceActionTargetId, readServiceAppManifest, readServiceAppSlotTarget, resolveAgentRuntimeEntries, resolveAutomaticUpdateCheckIntervalMs, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolvePortableRuntimeAcceptanceLocale, resolveSessionChannelContext, runGatewayInboundLoop, runNextclawTask, sanitizeLlmUsage, serializeContextTail, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, startPromptOverNcpExecution, stripSkillFrontmatter, syncSessionThinkingPreference, toBoundedJson, toNcpMessages, waitForAgentRuntimeSessionReply };
29683
29771
 
29684
29772
  //# sourceMappingURL=index.js.map