@nextclaw/kernel 0.6.0 → 0.6.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
@@ -1,7 +1,7 @@
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
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
- import { NcpEventType, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
4
+ import { NcpEventType, normalizeAssistantText, 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";
7
7
  import { CHAT_SESSION_MATERIALIZATION_METADATA_KEY, EventBus, Ingress, eventKeys, ingressKeys, isRuntimeDefaultModelValue, normalizeRuntimeModelSelectionMode } from "@nextclaw/shared";
@@ -203,8 +203,18 @@ function toLegacyMessages(messages, options = {}) {
203
203
  //#region src/features/context-compaction/utils/context-compaction.utils.ts
204
204
  const NEXTCLAW_TIMELINE_KIND_METADATA_KEY = "nextclaw_timeline_kind";
205
205
  const CONTEXT_COMPACTION_TIMELINE_KIND = "context_compaction";
206
+ const CONTEXT_COMPACTION_PROJECTION_METADATA_KEY = "nextclaw_context_projection";
207
+ const CONTEXT_COMPACTION_PROJECTION_KIND = "compressed_context";
206
208
  function readCheckpointTimelineText(checkpoint) {
207
- return checkpoint.status === "compressing" ? "正在压缩较早上下文" : "较早上下文已自动压缩";
209
+ return checkpoint.status === "compressing" ? "Compressing earlier context" : "Earlier context was auto-compacted";
210
+ }
211
+ function buildCompressedContextSystemText(checkpoint) {
212
+ return [
213
+ "Authoritative compressed prior conversation context for this session.",
214
+ "Continue from this context and the latest user message. 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.",
215
+ "",
216
+ checkpoint.summary
217
+ ].join("\n");
208
218
  }
209
219
  function readContextCompactionCheckpoint(message) {
210
220
  const metadata = message.metadata;
@@ -221,15 +231,19 @@ function buildContextCompactionSummaryMessage(params) {
221
231
  return {
222
232
  id: `${sessionId}:context-compaction-summary:${checkpoint.id}:${checkpoint.updatedAt}`,
223
233
  sessionId,
224
- role: "user",
234
+ role: "service",
225
235
  status: "final",
226
236
  timestamp: checkpoint.updatedAt,
227
237
  parts: [{
228
238
  type: "text",
229
- text: checkpoint.summary
230
- }]
239
+ text: buildCompressedContextSystemText(checkpoint)
240
+ }],
241
+ metadata: { [CONTEXT_COMPACTION_PROJECTION_METADATA_KEY]: CONTEXT_COMPACTION_PROJECTION_KIND }
231
242
  };
232
243
  }
244
+ function readCheckpointCoveredUntil(checkpoint) {
245
+ return checkpoint.coveredUntil ?? checkpoint.updatedAt;
246
+ }
233
247
  function createContextCompactionMessageId() {
234
248
  return `context-compaction-message-${randomUUID()}`;
235
249
  }
@@ -255,6 +269,9 @@ function buildContextCompactionTimelineNcpMessage(params) {
255
269
  function isContextCompactionTimelineMessage(message) {
256
270
  return message?.metadata?.[NEXTCLAW_TIMELINE_KIND_METADATA_KEY] === CONTEXT_COMPACTION_TIMELINE_KIND;
257
271
  }
272
+ function isContextCompactionProjectionMessage(message) {
273
+ return message?.metadata?.[CONTEXT_COMPACTION_PROJECTION_METADATA_KEY] === CONTEXT_COMPACTION_PROJECTION_KIND;
274
+ }
258
275
  function readLatestContextCompactionCheckpoint(sessionMessages) {
259
276
  return readLatestContextCompactionMarker(sessionMessages)?.checkpoint ?? null;
260
277
  }
@@ -264,10 +281,11 @@ function buildContextCompactionModelInput(params) {
264
281
  const regularMessages = sessionMessages.filter((message) => !readContextCompactionCheckpoint(message));
265
282
  if (!marker) return regularMessages.map((message) => structuredClone(message));
266
283
  const { checkpoint } = marker;
284
+ const coveredUntil = readCheckpointCoveredUntil(checkpoint);
267
285
  return [buildContextCompactionSummaryMessage({
268
286
  checkpoint,
269
287
  sessionId
270
- }), ...regularMessages.filter((message) => Date.parse(message.timestamp) > Date.parse(checkpoint.updatedAt)).sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp))].map((message) => structuredClone(message));
288
+ }), ...regularMessages.filter((message) => Date.parse(message.timestamp) > Date.parse(coveredUntil)).sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp))].map((message) => structuredClone(message));
271
289
  }
272
290
  function readContextWindowEventSessionId(event) {
273
291
  const payload = "payload" in event ? event.payload : null;
@@ -311,6 +329,17 @@ function shouldRefreshContextWindowImmediately(event) {
311
329
  //#region src/features/context-compaction/services/context-compaction-preflight.service.ts
312
330
  const SUMMARY_MAX_TOKENS = 4e3;
313
331
  const SUMMARY_SOURCE_MAX_CHARS = 12e4;
332
+ const SUMMARY_SOURCE_HEAD_MESSAGES = 2;
333
+ const SUMMARY_SOURCE_TAIL_MESSAGES = 8;
334
+ const SUMMARY_SOURCE_STRING_HEAD_CHARS = 6e3;
335
+ const SUMMARY_SOURCE_STRING_TAIL_CHARS = 6e3;
336
+ function buildContextBlockMessage(contextBlocks = []) {
337
+ const contextContent = contextBlocks.map((block) => block.trim()).filter(Boolean).join("\n\n");
338
+ return contextContent ? [{
339
+ role: "system",
340
+ content: contextContent
341
+ }] : [];
342
+ }
314
343
  function mergeInputMessages(params) {
315
344
  const messages = params.sessionMessages.map((message) => structuredClone(message));
316
345
  const seen = new Set(messages.map((message) => message.id));
@@ -320,10 +349,47 @@ function mergeInputMessages(params) {
320
349
  }
321
350
  return messages;
322
351
  }
352
+ function toCompactionSourceMessage(message) {
353
+ return {
354
+ role: message.role,
355
+ content: message.content,
356
+ timestamp: message.timestamp,
357
+ ncp_message_id: message.ncp_message_id
358
+ };
359
+ }
323
360
  function stringifyCompactionSource(messages) {
324
- const json = JSON.stringify(messages, null, 2);
361
+ const sourceMessages = messages.map(toCompactionSourceMessage);
362
+ const json = JSON.stringify(sourceMessages, null, 2);
325
363
  if (json.length <= SUMMARY_SOURCE_MAX_CHARS) return json;
326
- return `${json.slice(0, SUMMARY_SOURCE_MAX_CHARS).trimEnd()}\n[truncated_source]`;
364
+ const tailStart = Math.max(SUMMARY_SOURCE_HEAD_MESSAGES, sourceMessages.length - SUMMARY_SOURCE_TAIL_MESSAGES);
365
+ const compactedMessages = [
366
+ ...sourceMessages.slice(0, SUMMARY_SOURCE_HEAD_MESSAGES),
367
+ ...tailStart > SUMMARY_SOURCE_HEAD_MESSAGES ? [{
368
+ role: "system",
369
+ content: `[${tailStart - SUMMARY_SOURCE_HEAD_MESSAGES} middle messages omitted from compaction source]`
370
+ }] : [],
371
+ ...sourceMessages.slice(tailStart)
372
+ ];
373
+ const compactedJson = JSON.stringify(compactedMessages, (_key, value) => truncateSummarySourceString(value), 2);
374
+ if (compactedJson.length <= SUMMARY_SOURCE_MAX_CHARS) return compactedJson;
375
+ const marker = "\n[truncated_compaction_source_middle]\n";
376
+ const headChars = Math.floor((SUMMARY_SOURCE_MAX_CHARS - 38) / 2);
377
+ const tailChars = SUMMARY_SOURCE_MAX_CHARS - 38 - headChars;
378
+ return `${compactedJson.slice(0, headChars).trimEnd()}${marker}${compactedJson.slice(-tailChars).trimStart()}`;
379
+ }
380
+ function truncateSummarySourceString(value) {
381
+ if (typeof value === "string") {
382
+ if (value.length <= SUMMARY_SOURCE_STRING_HEAD_CHARS + SUMMARY_SOURCE_STRING_TAIL_CHARS) return value;
383
+ return [
384
+ value.slice(0, SUMMARY_SOURCE_STRING_HEAD_CHARS).trimEnd(),
385
+ `[${value.length - SUMMARY_SOURCE_STRING_HEAD_CHARS - SUMMARY_SOURCE_STRING_TAIL_CHARS} chars omitted]`,
386
+ value.slice(-SUMMARY_SOURCE_STRING_TAIL_CHARS).trimStart()
387
+ ].join("\n");
388
+ }
389
+ return value;
390
+ }
391
+ function normalizeCompactionSummary(content) {
392
+ return normalizeAssistantText(content, "think-tags").text.trim();
327
393
  }
328
394
  function buildContextWindowSnapshotFromBudget(params) {
329
395
  const { budget, checkpoint, totalContextTokens } = params;
@@ -347,7 +413,7 @@ var ContextCompactionPreflightService = class {
347
413
  this.providerManager = providerManager;
348
414
  }
349
415
  preview = (params) => {
350
- const { requestMetadata, sessionId, sessionMessages, storedAgentId, storedMetadata } = params;
416
+ const { contextBlocks = [], requestMetadata, sessionId, sessionMessages, storedAgentId, storedMetadata } = params;
351
417
  const profile = this.resolveCompactionProfile({
352
418
  requestMetadata,
353
419
  storedAgentId
@@ -357,9 +423,10 @@ var ContextCompactionPreflightService = class {
357
423
  sessionId,
358
424
  sessionMessages
359
425
  }) : sessionMessages.filter((message) => !isContextCompactionTimelineMessage(message));
426
+ const messages = [...buildContextBlockMessage(contextBlocks), ...toLegacyMessages(projectedMessages)];
360
427
  return buildContextWindowSnapshotFromBudget({
361
428
  budget: this.contextWindowBudgetService.evaluate({
362
- messages: toLegacyMessages(projectedMessages),
429
+ messages,
363
430
  contextTokens: profile.contextTokens,
364
431
  reservedContextTokens: profile.reservedContextTokens
365
432
  }),
@@ -368,7 +435,7 @@ var ContextCompactionPreflightService = class {
368
435
  });
369
436
  };
370
437
  begin = (params) => {
371
- const { inputMessages, requestMetadata, sessionId, sessionMessages, storedAgentId, storedMetadata } = params;
438
+ const { contextBlocks = [], inputMessages, requestMetadata, sessionId, sessionMessages, storedAgentId, storedMetadata } = params;
372
439
  const profile = this.resolveCompactionProfile({
373
440
  requestMetadata,
374
441
  storedAgentId
@@ -379,10 +446,11 @@ var ContextCompactionPreflightService = class {
379
446
  sessionMessages
380
447
  });
381
448
  const existingCheckpoint = readCompressedContextCompactionCheckpoint(storedMetadata[CONTEXT_COMPACTION_METADATA_KEY]) ?? readLatestContextCompactionCheckpoint(ncpMessages);
382
- const messages = toLegacyMessages(existingCheckpoint ? buildContextCompactionModelInput({
449
+ const projectedMessages = existingCheckpoint ? buildContextCompactionModelInput({
383
450
  sessionId,
384
451
  sessionMessages: ncpMessages
385
- }) : ncpMessages.filter((message) => !isContextCompactionTimelineMessage(message)));
452
+ }) : ncpMessages.filter((message) => !isContextCompactionTimelineMessage(message));
453
+ const messages = [...buildContextBlockMessage(contextBlocks), ...toLegacyMessages(projectedMessages)];
386
454
  const budget = this.contextWindowBudgetService.evaluate({
387
455
  messages,
388
456
  contextTokens,
@@ -468,7 +536,7 @@ var ContextCompactionPreflightService = class {
468
536
  generateSummary = async (params) => {
469
537
  if (!this.providerManager) throw new Error("context compaction summary generation requires a provider manager");
470
538
  const { messages, model } = params;
471
- const summary = (await this.providerManager.chat({
539
+ const response = await this.providerManager.chat({
472
540
  model,
473
541
  maxTokens: SUMMARY_MAX_TOKENS,
474
542
  messages: [{
@@ -476,7 +544,10 @@ var ContextCompactionPreflightService = class {
476
544
  content: [
477
545
  "You are NextClaw's context compactor for a coding agent session.",
478
546
  "Create a complete compressed working context that will replace all prior conversation messages in a future model request.",
479
- "The model will not receive a raw recent-message tail, so preserve the latest user intent and recent turns with high fidelity inside the summary.",
547
+ "Only the latest current input may remain raw, so preserve the active task, latest user intent, latest assistant response, and recent turns with high fidelity inside the summary.",
548
+ "Always include a 'Continuation Contract' section that states what the next assistant response should remember and how it should continue the session.",
549
+ "Do not turn missing user profile, assistant nickname, or onboarding fields into blockers unless onboarding is the active user task in the latest turns.",
550
+ "If the latest user message is a short greeting, preserve the prior active task and last assistant stance so the next response does not restart as a fresh session.",
480
551
  "Preserve user goals, explicit instructions, decisions, files touched or inspected, code changes, commands run, test results, failures, blockers, current task state, and exact next steps.",
481
552
  "Do not invent facts. If something is uncertain, mark it as uncertain.",
482
553
  "Return Markdown only. Start with '# Compressed Working Context'."
@@ -486,12 +557,14 @@ var ContextCompactionPreflightService = class {
486
557
  content: [
487
558
  "Compress these runtime messages into a reusable working context.",
488
559
  "Include a 'Recent High-Fidelity Context' section for the latest important user/assistant turns.",
560
+ "Include a 'Continuation Contract' section after the recent context.",
489
561
  "",
490
562
  "Messages JSON:",
491
563
  stringifyCompactionSource(messages)
492
564
  ].join("\n")
493
565
  }]
494
- })).content?.trim();
566
+ });
567
+ const summary = response.content ? normalizeCompactionSummary(response.content) : "";
495
568
  if (!summary) throw new Error("context compaction summary is empty");
496
569
  return summary;
497
570
  };
@@ -535,6 +608,7 @@ var AgentRunContextCompactionManager = class {
535
608
  }
536
609
  runPreflight = async (input) => {
537
610
  const beginResult = this.preflightService.begin({
611
+ contextBlocks: input.contextBlocks,
538
612
  inputMessages: [],
539
613
  requestMetadata: input.metadata,
540
614
  sessionId: input.sessionId,
@@ -748,7 +822,12 @@ var AgentRunRequestManager = class {
748
822
  };
749
823
  handleAbortRequest = async (envelope) => {
750
824
  if (!envelope.payload?.sessionId) throw new Error("Invalid agent run abort request.");
751
- await this.abort({ sessionId: envelope.payload.sessionId });
825
+ await this.abort({
826
+ sessionId: envelope.payload.sessionId,
827
+ runId: envelope.payload.runId,
828
+ correlationId: envelope.payload.correlationId,
829
+ reason: envelope.payload.reason
830
+ });
752
831
  };
753
832
  handleSessionMessageRequest = async (envelope) => {
754
833
  if (!envelope.payload) throw new Error("Invalid agent run session message request.");
@@ -895,7 +974,7 @@ var AgentRunRequestManager = class {
895
974
  });
896
975
  };
897
976
  abort = async (request) => {
898
- this.sessionRunManager.getSessionRun(request.sessionId)?.abortRun(request.runId);
977
+ this.sessionRunManager.getSessionRun(request.sessionId)?.abortRun(request.runId, request.reason);
899
978
  };
900
979
  };
901
980
  //#endregion
@@ -3887,12 +3966,12 @@ function createProjection(sessionId, preview) {
3887
3966
  };
3888
3967
  }
3889
3968
  function formatErrorStatus(error) {
3890
- if (typeof error === "string" && error.trim()) return `运行出错:${truncatePreviewText(error)}`;
3969
+ if (typeof error === "string" && error.trim()) return `Run failed: ${truncatePreviewText(error)}`;
3891
3970
  if (error && typeof error === "object" && "message" in error) {
3892
3971
  const message = error.message;
3893
- if (typeof message === "string" && message.trim()) return `运行出错:${truncatePreviewText(message)}`;
3972
+ if (typeof message === "string" && message.trim()) return `Run failed: ${truncatePreviewText(message)}`;
3894
3973
  }
3895
- return "运行出错";
3974
+ return "Run failed";
3896
3975
  }
3897
3976
  function readToolCallId(value) {
3898
3977
  if (typeof value !== "string") return null;
@@ -3900,13 +3979,13 @@ function readToolCallId(value) {
3900
3979
  return trimmed.length > 0 ? trimmed : null;
3901
3980
  }
3902
3981
  function formatToolDoneStatus(toolName) {
3903
- return toolName ? `工具调用完成:${toolName}` : "工具调用完成";
3982
+ return toolName ? `Tool call completed: ${toolName}` : "Tool call completed";
3904
3983
  }
3905
3984
  function createSessionActivityPreviewFromNcpEvent(event, timestamp, options = {}) {
3906
3985
  switch (event.type) {
3907
3986
  case NcpEventType.RunStarted: return createProjection(readSessionId(event.payload.sessionId), {
3908
3987
  state: "running",
3909
- statusText: "正在思考",
3988
+ statusText: "Thinking",
3910
3989
  timestamp
3911
3990
  });
3912
3991
  case NcpEventType.RunFinished: return createProjection(readSessionId(event.payload.sessionId), {
@@ -3941,9 +4020,13 @@ function createSessionActivityPreviewFromNcpEvent(event, timestamp, options = {}
3941
4020
  statusText: formatErrorStatus(event.payload.error),
3942
4021
  timestamp
3943
4022
  });
4023
+ case NcpEventType.MessageAbort: return createProjection(readSessionId(event.payload.sessionId), {
4024
+ state: "cancelled",
4025
+ timestamp
4026
+ });
3944
4027
  case NcpEventType.MessageToolCallStart: return createProjection(readSessionId(event.payload.sessionId), {
3945
4028
  state: "running",
3946
- statusText: `正在调用工具:${event.payload.toolName}`,
4029
+ statusText: `Calling tool: ${event.payload.toolName}`,
3947
4030
  timestamp
3948
4031
  });
3949
4032
  case NcpEventType.MessageToolCallEnd:
@@ -3966,6 +4049,7 @@ const SESSION_ACTIVITY_PREVIEW_STATES = new Set([
3966
4049
  "running",
3967
4050
  "completed",
3968
4051
  "failed",
4052
+ "cancelled",
3969
4053
  "idle"
3970
4054
  ]);
3971
4055
  function isRecord$9(value) {
@@ -4053,7 +4137,7 @@ var SessionActivityPreviewEventService = class {
4053
4137
  };
4054
4138
  readToolName = (sessionId, toolCallId) => this.toolNames.get(this.createToolNameKey(sessionId, toolCallId)) ?? null;
4055
4139
  clearFinishedRunToolNames = (event) => {
4056
- if (event.type !== NcpEventType.RunFinished && event.type !== NcpEventType.RunError) return;
4140
+ if (event.type !== NcpEventType.RunFinished && event.type !== NcpEventType.RunError && event.type !== NcpEventType.MessageAbort) return;
4057
4141
  const sessionId = event.payload.sessionId;
4058
4142
  for (const key of this.toolNames.keys()) if (key.startsWith(`${sessionId}:`)) this.toolNames.delete(key);
4059
4143
  };
@@ -6718,11 +6802,11 @@ var SessionRun = class {
6718
6802
  signal: controller.signal
6719
6803
  };
6720
6804
  };
6721
- abortRun = (runId) => {
6805
+ abortRun = (runId, reason) => {
6722
6806
  if (!this.activeRunId || !this.activeRunController) return false;
6723
6807
  if (runId && this.activeRunId !== runId) return false;
6724
6808
  const wasRunning = this.isRunning();
6725
- this.activeRunController.abort();
6809
+ this.activeRunController.abort(reason);
6726
6810
  this.activeRunController = null;
6727
6811
  this.activeRunId = null;
6728
6812
  this.emitStatusChangeIfNeeded(wasRunning);
@@ -6731,7 +6815,11 @@ var SessionRun = class {
6731
6815
  isRunning = () => this.activeRunId !== null;
6732
6816
  dispose = () => {
6733
6817
  const wasRunning = this.isRunning();
6734
- this.activeRunController?.abort();
6818
+ this.activeRunController?.abort({
6819
+ code: "abort-error",
6820
+ message: "Session run owner was disposed; the current run was cancelled.",
6821
+ details: { source: "session-run-manager" }
6822
+ });
6735
6823
  this.activeRunController = null;
6736
6824
  this.activeRunId = null;
6737
6825
  this.emitStatusChangeIfNeeded(wasRunning);
@@ -8392,7 +8480,61 @@ var AgentRunModelInputBudgeter = class {
8392
8480
  };
8393
8481
  };
8394
8482
  //#endregion
8483
+ //#region src/utils/agent-onboarding-context.utils.ts
8484
+ const ALWAYS_SKIPPED_COMPACTED_BOOTSTRAP_FILES = new Set(["BOOT.MD", "BOOTSTRAP.MD"]);
8485
+ function normalizeBootstrapFilename(filename) {
8486
+ return filename.trim().toUpperCase();
8487
+ }
8488
+ function shouldSkipCompactedSessionBootstrapFile(filename, content) {
8489
+ const normalized = normalizeBootstrapFilename(filename);
8490
+ if (ALWAYS_SKIPPED_COMPACTED_BOOTSTRAP_FILES.has(normalized)) return true;
8491
+ if (normalized === "IDENTITY.MD") return /Fill this in during your first conversation/i.test(content);
8492
+ if (normalized === "USER.MD") return /Learn about the person you are helping/i.test(content);
8493
+ return false;
8494
+ }
8495
+ function stripCompactedSessionOnboardingSections(block) {
8496
+ const lines = block.split("\n");
8497
+ const output = [];
8498
+ let index = 0;
8499
+ while (index < lines.length) {
8500
+ const line = lines[index] ?? "";
8501
+ const heading = line.match(/^##\s+(.+?)\s*$/);
8502
+ if (!heading) {
8503
+ output.push(line);
8504
+ index += 1;
8505
+ continue;
8506
+ }
8507
+ const sectionLines = [line];
8508
+ index += 1;
8509
+ while (index < lines.length && !/^##\s+/.test(lines[index] ?? "")) {
8510
+ sectionLines.push(lines[index] ?? "");
8511
+ index += 1;
8512
+ }
8513
+ if (shouldSkipCompactedSessionBootstrapFile(heading[1] ?? "", sectionLines.join("\n"))) continue;
8514
+ output.push(...sectionLines);
8515
+ }
8516
+ return output.join("\n").trim();
8517
+ }
8518
+ //#endregion
8395
8519
  //#region src/services/agent-run-model-input-builder.service.ts
8520
+ function readSystemContent(messages) {
8521
+ return messages.filter((message) => message.role === "system").map((message) => message.content.trim()).filter(Boolean);
8522
+ }
8523
+ function partitionProjectedMessages(messages) {
8524
+ const compressedContextBlocks = [];
8525
+ const conversationMessages = [];
8526
+ for (const message of messages) {
8527
+ if (!isContextCompactionProjectionMessage(message)) {
8528
+ conversationMessages.push(message);
8529
+ continue;
8530
+ }
8531
+ compressedContextBlocks.push(...readSystemContent(ncpMessageToOpenAiMessages(message)));
8532
+ }
8533
+ return {
8534
+ compressedContextBlocks,
8535
+ conversationMessages
8536
+ };
8537
+ }
8396
8538
  var AgentRunModelInputBuilder = class {
8397
8539
  constructor(messageProjector, modelInputBudgeter, assetStore = null) {
8398
8540
  this.messageProjector = messageProjector;
@@ -8400,15 +8542,17 @@ var AgentRunModelInputBuilder = class {
8400
8542
  this.assetStore = assetStore;
8401
8543
  }
8402
8544
  build = async (request) => {
8403
- const contextContent = request.contextBlocks.map((block) => block.trim()).filter(Boolean).join("\n\n");
8545
+ const { compressedContextBlocks, conversationMessages: projectedConversationMessages } = partitionProjectedMessages(this.messageProjector.project({
8546
+ sessionId: request.sessionId,
8547
+ messages: request.messages
8548
+ }));
8549
+ const contextBlocks = compressedContextBlocks.length > 0 ? request.contextBlocks.map(stripCompactedSessionOnboardingSections) : request.contextBlocks;
8550
+ const contextContent = [...compressedContextBlocks, ...contextBlocks].map((block) => block.trim()).filter(Boolean).join("\n\n");
8404
8551
  const contextMessages = contextContent ? [{
8405
8552
  role: "system",
8406
8553
  content: contextContent
8407
8554
  }] : [];
8408
- const conversationMessages = this.messageProjector.project({
8409
- sessionId: request.sessionId,
8410
- messages: request.messages
8411
- }).flatMap((message) => ncpMessageToOpenAiMessages(message, { assetStore: this.assetStore }));
8555
+ const conversationMessages = projectedConversationMessages.flatMap((message) => ncpMessageToOpenAiMessages(message, { assetStore: this.assetStore }));
8412
8556
  const pruned = await this.modelInputBudgeter.prune({
8413
8557
  spec: request.spec,
8414
8558
  messages: [...contextMessages, ...conversationMessages]
@@ -8522,10 +8666,11 @@ var AgentRunRuntimeContribution = class {
8522
8666
  createRuntime: () => new DefaultNcpAgentRuntime({
8523
8667
  llmApi: new ProviderManagerNcpLLMApi(this.kernel.llmProviders),
8524
8668
  modelInputBuilder: this.modelInputBuilder,
8525
- runPreflight: async ({ spec, sessionRun }) => {
8669
+ runPreflight: async ({ contextBlocks, spec, sessionRun }) => {
8526
8670
  const session = await this.kernel.sessionManager.getAgentRunSession(sessionRun.sessionId);
8527
8671
  return await this.kernel.contextCompactionManager.runPreflight({
8528
8672
  agentId: spec.agentId,
8673
+ contextBlocks,
8529
8674
  messages: sessionRun.getSnapshot().messages,
8530
8675
  metadata: session.metadata,
8531
8676
  sessionId: sessionRun.sessionId
@@ -8570,11 +8715,13 @@ var AgentBootstrapContextProvider = class {
8570
8715
  provide = async (request) => {
8571
8716
  const { contextConfig, projectContext, runContext } = await this.context.resolve(request);
8572
8717
  const budget = this.createReadBudget(contextConfig.bootstrap);
8718
+ const compactedSession = this.hasCompressedContext(runContext.sessionMetadata);
8573
8719
  const agentBootstrapRoot = projectContext.projectBootstrapRoot ?? projectContext.effectiveWorkspace;
8574
8720
  const projectBootstrap = this.loadBootstrapFiles({
8575
8721
  root: agentBootstrapRoot,
8576
8722
  config: contextConfig.bootstrap,
8577
8723
  sessionKey: runContext.sessionKey,
8724
+ compactedSession,
8578
8725
  budget
8579
8726
  });
8580
8727
  const hasDistinctHostWorkspace = projectContext.hostWorkspace !== agentBootstrapRoot;
@@ -8582,6 +8729,7 @@ var AgentBootstrapContextProvider = class {
8582
8729
  root: projectContext.hostWorkspace,
8583
8730
  config: contextConfig.bootstrap,
8584
8731
  sessionKey: runContext.sessionKey,
8732
+ compactedSession,
8585
8733
  budget
8586
8734
  }) : "";
8587
8735
  const hasSoulFile = /##\s+SOUL\.md\b/i.test(`${projectBootstrap}\n${workspaceBootstrap}`);
@@ -8615,14 +8763,15 @@ var AgentBootstrapContextProvider = class {
8615
8763
  return lines.join("\n");
8616
8764
  };
8617
8765
  loadBootstrapFiles = (params) => {
8618
- const { budget, config, root, sessionKey } = params;
8766
+ const { budget, compactedSession, config, root, sessionKey } = params;
8619
8767
  const parts = [];
8620
- const fileList = this.selectBootstrapFiles(config, sessionKey);
8768
+ const fileList = this.selectBootstrapFiles(config, sessionKey, compactedSession);
8621
8769
  for (const filename of fileList) {
8622
8770
  const filePath = join(root, filename);
8623
8771
  if (!existsSync(filePath)) continue;
8624
8772
  const raw = readFileSync(filePath, "utf-8").trim();
8625
8773
  if (!raw) continue;
8774
+ if (compactedSession && shouldSkipCompactedSessionBootstrapFile(filename, raw)) continue;
8626
8775
  const perFileLimit = config.perFileChars > 0 ? config.perFileChars : raw.length;
8627
8776
  const allowed = Math.min(perFileLimit, budget.remaining);
8628
8777
  if (allowed <= 0) break;
@@ -8634,11 +8783,13 @@ var AgentBootstrapContextProvider = class {
8634
8783
  return parts.join("\n\n");
8635
8784
  };
8636
8785
  createReadBudget = (config) => ({ remaining: config.totalChars > 0 ? config.totalChars : Number.POSITIVE_INFINITY });
8637
- selectBootstrapFiles = (config, sessionKey) => {
8638
- if (!sessionKey) return config.files;
8786
+ selectBootstrapFiles = (config, sessionKey, compactedSession = false) => {
8787
+ if (!sessionKey) return this.filterCompactedSessionFiles(config.files, compactedSession);
8639
8788
  if (sessionKey.startsWith("cron:") || sessionKey.startsWith("subagent:")) return config.minimalFiles;
8640
- return config.files;
8789
+ return this.filterCompactedSessionFiles(config.files, compactedSession);
8641
8790
  };
8791
+ filterCompactedSessionFiles = (files, compactedSession) => compactedSession ? files.filter((filename) => !shouldSkipCompactedSessionBootstrapFile(filename, "")) : [...files];
8792
+ hasCompressedContext = (metadata) => Boolean(readCompressedContextCompactionCheckpoint(metadata?.[CONTEXT_COMPACTION_METADATA_KEY]));
8642
8793
  };
8643
8794
  //#endregion
8644
8795
  //#region src/contributions/context-provider/providers/current-session-context.provider.ts
@@ -8720,7 +8871,7 @@ const createInlineInteractiveSurfaceContextProvider = () => staticBlock([
8720
8871
  "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.",
8721
8872
  "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
8873
  "`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.",
8874
+ "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. Markdown file links open source by default; append `?viewer=rendered` only when the link itself should open the rendered HTML view. Do not convert a plain HTML file into a Panel App just to preview it.",
8724
8875
  "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.",
8725
8876
  "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."
8726
8877
  ]);
@@ -8877,7 +9028,7 @@ var ProjectContextProvider = class {
8877
9028
  //#endregion
8878
9029
  //#region src/contributions/context-provider/providers/reply-format-context.provider.ts
8879
9030
  var ReplyFormatContextProvider = class {
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."];
9031
+ 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. File links open source by default; use a viewer query such as [preview.html](preview.html?viewer=rendered) only when the link should open the rendered HTML view.\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."];
8881
9032
  };
8882
9033
  //#endregion
8883
9034
  //#region src/contributions/context-provider/providers/skills-context.provider.ts
@@ -9993,7 +10144,7 @@ function normalizeShowFileArgs(args) {
9993
10144
  path: readRequiredString(params.path, "path"),
9994
10145
  line: readOptionalPositiveInteger(params.line, "line"),
9995
10146
  column: readOptionalPositiveInteger(params.column, "column"),
9996
- viewer: readOptionalEnum(params.viewer, "viewer", FILE_VIEWERS)
10147
+ viewer: readOptionalEnum(params.viewer, "viewer", FILE_VIEWERS) ?? "source"
9997
10148
  }
9998
10149
  },
9999
10150
  ...readCommonRequestFields(params, FILE_PURPOSES)
@@ -10062,7 +10213,7 @@ var ShowContentDisplayTool = class {
10062
10213
  const SHOW_CONTENT_TOOL_SPECS = [
10063
10214
  {
10064
10215
  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.",
10216
+ description: "Show a local file in the current chat UI. Defaults to source text. Use viewer=\"rendered\" for rendered HTML/page previews and viewer=\"source\" for source text.",
10066
10217
  parameters: {
10067
10218
  type: "object",
10068
10219
  properties: {
@@ -10967,6 +11118,6 @@ function resolveLegacyEventType(message) {
10967
11118
  return `message.${role || "other"}`;
10968
11119
  }
10969
11120
  //#endregion
10970
- export { AccessManager, AgentManager, AgentRunClient, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionPreflightService, ContextWindowPreviewManager, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, ExtensionManager, GatewayInboundProcessor, LlmProviderManager, LlmUsageManager, LlmUsageStore, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawKernel, PANEL_APP_AGENT_CAPABILITIES, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PreferenceError, PreferenceManager, ProviderManagerNcpLLMApi, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppError, ServiceAppManager, SessionManager, SessionRequestManager, SkillManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionModelInput, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildServiceActionId, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextCompactionMessageId, createContextWindowSignature, createLlmUsageRecord, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getServiceActionName, getServiceAppManifestPath, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isContextCompactionTimelineMessage, isContextWindowSnapshot, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isReplyCapableChannel, isServiceAppError, listExtensionChannelIds, listServiceAppManifestActions, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseServiceAppManifest, parseSkillFrontmatter, readContextWindowEventSessionId, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceAppManifest, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, waitForAgentRuntimeSessionReply };
11121
+ export { AccessManager, AgentManager, AgentRunClient, AutomationManager, BuiltinNarpRuntimeProviderService, CONTEXT_COMPACTION_PROJECTION_KIND, CONTEXT_COMPACTION_PROJECTION_METADATA_KEY, CONTEXT_COMPACTION_TIMELINE_KIND, ChannelManager, CommandRegistry, ConfigManager, ContextCompactionPreflightService, ContextWindowPreviewManager, DEFAULT_AGENT_RUNTIME_ENTRY_ID, DEFAULT_SERVICE_ACTION_RISK, ExtensionManager, GatewayInboundProcessor, LlmProviderManager, LlmUsageManager, LlmUsageStore, McpManager, McpServiceAppRuntimeService, NARP_HTTP_RUNTIME_KIND, NARP_STDIO_RUNTIME_KIND, NEXTCLAW_TIMELINE_KIND_METADATA_KEY, NcpAgentSessionJournalStore, NextclawKernel, PANEL_APP_AGENT_CAPABILITIES, PanelAppAssetTokenService, PanelAppError, PanelAppManager, PreferenceError, PreferenceManager, ProviderManagerNcpLLMApi, SERVICE_APP_MANIFEST_FILE_NAME, ServiceAppError, ServiceAppManager, SessionManager, SessionRequestManager, SkillManager, UpdateManifestReader, buildAgentRunSendPayload, buildContextCompactionModelInput, buildContextCompactionTimelineNcpMessage, buildLlmUsageSummary, buildLocalizedTextMap, buildNextclawNcpRunContext, buildServiceActionId, createAgentRuntimeSessionRequestDispatcher, createAssetTools, createContextCompactionMessageId, createContextWindowSignature, createLlmUsageRecord, describeAgentRuntimeSessionTypes, dispatchAgentRuntimeSessionRequest, dispatchChannelReplyRoute, dispatchPromptOverNcp, getServiceActionName, getServiceAppManifestPath, getUnsignedUpdateManifest, hasLlmUsageTelemetry, isContextCompactionProjectionMessage, isContextCompactionTimelineMessage, isContextWindowSnapshot, isPanelAppAgentCapability, isPanelAppError, isPreferenceError, isReplyCapableChannel, isServiceAppError, listExtensionChannelIds, listServiceAppManifestActions, mergeServiceAppRuntimeActions, normalizeAgentRuntimeSessionTypeIcon, normalizeLlmUsageModel, normalizeOptionalString, parseServiceAppManifest, parseSkillFrontmatter, readContextWindowEventSessionId, readLatestContextCompactionCheckpoint, readLearningLoopRuntimeConfig, readMetadataModel, readMetadataThinking, readServiceAppManifest, resolveAgentRuntimeEntries, resolveChannelReplyRoute, resolveEffectiveModel, resolveLegacyEventType, resolveSessionChannelContext, runGatewayInboundLoop, sanitizeLlmUsage, serializeUnsignedUpdateManifest, shouldRefreshContextWindowDuringStream, shouldRefreshContextWindowImmediately, stripSkillFrontmatter, syncSessionThinkingPreference, toNcpMessages, waitForAgentRuntimeSessionReply };
10971
11122
 
10972
11123
  //# sourceMappingURL=index.js.map