newmark-agent 0.3.6 → 0.3.8

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.
@@ -327011,6 +327011,8 @@ var NATIVE_TOOL_CATALOG = [
327011
327011
  { name: "subagent_close", label: "Subagent close", description: "Close a same-conversation peer agent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327012
327012
  { name: "linked_plan", label: "Linked plan", description: "Read or conservatively update the conversation-linked Markdown plan.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327013
327013
  { name: "build_history_query", label: "Build history query", description: "Read concrete public work details for one historical Build Block.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327014
+ { name: "context_compress", label: "Context compress", description: "Actively compress the LLM context history, leaving the displayed conversation history unchanged.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327015
+ { name: "context_history_manage", label: "Context history manage", description: "List, remove, or summarize entries in the LLM context history without touching the displayed conversation history.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327014
327016
  { name: "question", label: "Ask question", description: "Ask the user for structured option feedback.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327015
327017
  { name: "skill_download", label: "Skill download", description: "Download and install a skill.", category: "agent", defaultEnabled: true },
327016
327018
  { name: "skill", label: "Skill", description: "Search enabled skill metadata or load one skill body on demand.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
@@ -327817,6 +327819,8 @@ function defaultConfig() {
327817
327819
  auto_compress: { _description: "Auto-compress history", _type: "boolean", value: true },
327818
327820
  compress_threshold_chars: { _description: "Compression threshold", _type: "integer", value: 8e4 },
327819
327821
  keep_recent_messages: { _description: "Keep recent messages", _type: "integer", value: 10 },
327822
+ preserve_recent_messages: { _description: "dev-0.3.8 protected recent-message zone for context_history_manage (0 disables)", _type: "integer", value: 5 },
327823
+ compression_cache_max: { _description: "dev-0.3.8 max folded-segment cache entries retained for restore/search", _type: "integer", value: 8 },
327820
327824
  structured_context_v2: { _description: "dev-0.3.0 structured context v2 (orchestrator + fixed order + snapshot)", _type: "boolean", value: true },
327821
327825
  build_history_persistence: { _description: "dev-0.3.0 append-only Build History persistence", _type: "boolean", value: true },
327822
327826
  branch_log_v2: { _description: "dev-0.3.0 branch long-log v2 (epoch summaries)", _type: "boolean", value: true },
@@ -334501,6 +334505,8 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
334501
334505
  "pdf_read",
334502
334506
  "linked_plan",
334503
334507
  "build_history_query",
334508
+ "context_compress",
334509
+ "context_history_manage",
334504
334510
  "question",
334505
334511
  "task",
334506
334512
  "subagent_list",
@@ -335522,14 +335528,24 @@ var ToolExecutor = class {
335522
335528
  remote_root: { type: "string" },
335523
335529
  remote_path: { type: "string" }
335524
335530
  }, ["action"]),
335525
- t3("task", "Create a same-conversation peer agent and return immediately. The peer has a nature slug, short id, and canonical UUID-qualified name. Pass model to select an exact configured model deployment (deployment:providerId:modelId or an unambiguous provider/model name). When model is omitted, the peer inherits the parent Agent's currently resolved model deployment. Plan mode peers are forced to Plan.", { nature: { type: "string" }, name: { type: "string", description: "Legacy alias for nature." }, prompt: { type: "string" }, preset: { type: "string" }, agent: { type: "string" }, model: { type: "string", description: "Optional exact model deployment. Omit to inherit the parent Agent resolved model." }, mode: { type: "string" }, input_mode: { type: "string" }, flow: { type: "string" } }, ["prompt"]),
335526
- t3("subagent_list", "List flat same-conversation peer agents, optionally filtered by status.", { status: { type: "string", enum: ["idle", "queued", "working", "completed", "error", "closed"] } }, []),
335527
- t3("subagent_read", "Read one same-conversation peer status, queue/mailbox summary, latest bounded feedback, and result. Available for running, queued, completed, error, and closed peers.", { id: { type: "string" }, name: { type: "string" }, max_chars: { type: "number", description: "Bounded result size from 2000 to 32000 characters." } }, []),
335528
- t3("subagent_send", "Persist a mailbox message to a same-conversation peer agent.", { id: { type: "string" }, name: { type: "string" }, message: { type: "string" }, prompt: { type: "string", description: "Legacy alias for message." }, kind: { type: "string", enum: ["directive", "question", "result", "handoff"] }, reply_to: { type: "string" }, correlation_id: { type: "string" } }, []),
335529
- t3("subagent_result", "Return the persisted transcript, mailbox summary, status, and latest result for a peer agent.", { id: { type: "string" }, name: { type: "string" } }, []),
335530
- t3("subagent_close", "Close a same-conversation peer. Root can close any peer; a peer can close only itself.", { id: { type: "string" }, name: { type: "string" } }, []),
335531
+ t3("task", "Create a same-conversation peer agent and return immediately. The peer has a stable human-readable name, a short id, and a canonical UUID-qualified identity. The peer name is decoupled from its id: pass name for readable references and id for exact targeting. Pass model to select an exact configured model deployment (deployment:providerId:modelId or an unambiguous provider/model name). When model is omitted, the peer inherits the parent Agent's currently resolved model deployment. Plan mode peers are forced to Plan.", { nature: { type: "string" }, name: { type: "string", description: "Legacy alias for nature." }, prompt: { type: "string" }, preset: { type: "string" }, agent: { type: "string" }, model: { type: "string", description: "Optional exact model deployment. Omit to inherit the parent Agent resolved model." }, mode: { type: "string" }, input_mode: { type: "string" }, flow: { type: "string" } }, ["prompt"]),
335532
+ t3("subagent_list", "List flat same-conversation peer agents, optionally filtered by status. Each entry exposes both the stable name and the exact id; use the id for any subsequent targeting.", { status: { type: "string", enum: ["idle", "queued", "working", "completed", "error", "closed"] } }, []),
335533
+ t3("subagent_read", "Read one same-conversation peer status, queue/mailbox summary, latest bounded feedback, and result. Available for running, queued, completed, error, and closed peers. Pass the exact id returned by subagent_list, or a name for convenience lookup.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name; ambiguous names resolve to the first match." }, max_chars: { type: "number", description: "Bounded result size from 2000 to 32000 characters." } }, []),
335534
+ t3("subagent_send", "Persist a mailbox message to a same-conversation peer agent. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." }, message: { type: "string" }, prompt: { type: "string", description: "Legacy alias for message." }, kind: { type: "string", enum: ["directive", "question", "result", "handoff"] }, reply_to: { type: "string" }, correlation_id: { type: "string" } }, []),
335535
+ t3("subagent_result", "Return the persisted transcript, mailbox summary, status, and latest result for a peer agent. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." } }, []),
335536
+ t3("subagent_close", "Close a same-conversation peer. Root can close any peer; a peer can close only itself. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." } }, []),
335531
335537
  t3("linked_plan", "Read or update the current conversation linked Markdown plan. Update requires the current expected_revision.", { action: { type: "string", enum: ["get", "update"] }, markdown: { type: "string" }, expected_revision: { type: "number" } }, ["action"]),
335532
335538
  t3("build_history_query", "Read the concrete public work details of one historical Build Block. The prompt already exposes user input, final summary, and completion status; call this read-only tool only when the user asks what specifically happened. Select by newest-to-oldest history_index, or by run_id returned from an earlier query.", { history_index: { type: "number", minimum: 1, description: "1-based historical Build Block index from the request ledger; 1 is the newest previous task." }, run_id: { type: "string", description: "Exact run id returned by an earlier build_history_query result." }, max_events: { type: "number", minimum: 1, maximum: 200, description: "Maximum trailing public work events; defaults to 80." } }, []),
335539
+ t3("context_compress", "Actively compress the LLM context history for this conversation. This collapses older history entries into a concise summary while preserving the recent tail, which reduces context tokens and cost. IMPORTANT: it affects only the LLM context (what the model sees); the displayed conversation history shown to the user is never altered. Call this when the conversation is long, token pressure is high, or you judge that older turns are no longer needed in full. Idempotent and safe: repeated calls produce incremental summaries.", { keep_recent: { type: "number", minimum: 2, maximum: 60, description: "Recent message count to keep uncompressed at the tail. Defaults to the configured keep_recent_messages." }, force: { type: "boolean", description: "Compress even if the context is not yet over the automatic threshold. Defaults to false." } }, []),
335540
+ t3("context_history_manage", "Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. Actions: list returns a bounded index of context entries (position, role, name, length, first-line preview); remove deletes the entry at a given position; summarize replaces a contiguous range of context entries with a concise local summary entry; restore reinserts the original messages of a folded segment from the compression cache using restore_id; search finds which cached folded segments contain a query (and the matching lines); status reports usage vs trigger/target budgets, the last compression, the compression cache, and the protected recent-message zone. The displayed conversation history (what the user sees) is never modified by any action. The recent context tail and the last user message are protected from remove/summarize unless dangerous is true.", {
335541
+ action: { type: "string", enum: ["list", "remove", "summarize", "restore", "search", "status"], description: "list: index context entries. remove: delete the entry at position. summarize: fold entries [from, to] into one summary entry. restore: reinsert cached original messages by restore_id. search: find folded cache entries containing query. status: report context usage, budgets, cache, and protected zone." },
335542
+ position: { type: "number", minimum: 0, description: "0-based context entry index for remove, or the start of the range for summarize." },
335543
+ to: { type: "number", minimum: 0, description: "0-based inclusive end of the range for summarize. Defaults to position." },
335544
+ limit: { type: "number", minimum: 5, maximum: 400, description: "Maximum context entries to list (default 200), or search matches to return (default 20)." },
335545
+ restore_id: { type: "string", description: "Cache id of a folded segment (from search or status) to restore into context." },
335546
+ query: { type: "string", description: "Case-insensitive text to search for across cached folded segments and their summaries." },
335547
+ dangerous: { type: "boolean", description: "Set true to override the protected recent-message zone and allow removing/summarizing entries that include the recent context tail or the last user message." }
335548
+ }, ["action"]),
335533
335549
  t3("question", "Ask user a multiple-choice question", { questions: { type: "array" } }, ["questions"]),
335534
335550
  t3("skill_download", "Download a skill", { name: { type: "string" }, source: { type: "string" } }, ["name", "source"]),
335535
335551
  t3("skill", "Search enabled skill metadata or load one exact skill body on demand. Use query when unsure, then name to load the selected skill.", { query: { type: "string", maxLength: 200 }, name: { type: "string", maxLength: 200 } }, []),
@@ -337615,7 +337631,7 @@ var SubagentManager = class {
337615
337631
  natureSlug: slug,
337616
337632
  displayName,
337617
337633
  qualifiedName,
337618
- name: qualifiedName,
337634
+ name: slug,
337619
337635
  conversationId: this.conversationId,
337620
337636
  createdByAgentId,
337621
337637
  prompt,
@@ -337626,7 +337642,7 @@ var SubagentManager = class {
337626
337642
  flowName: flowName || void 0,
337627
337643
  flowPc: Math.max(0, Math.floor(Number(flowPc) || 0)),
337628
337644
  status: "queued",
337629
- messages: [{ role: "system", content: `Peer agent '${qualifiedName}': ${prompt}` }, { role: "user", content: prompt, hidden_user_input: true }],
337645
+ messages: [{ role: "system", content: `Peer agent '${slug}' (${id}): ${prompt}` }, { role: "user", content: prompt, hidden_user_input: true }],
337630
337646
  result: null,
337631
337647
  createdAt: stamp,
337632
337648
  updatedAt: stamp
@@ -337636,7 +337652,10 @@ var SubagentManager = class {
337636
337652
  return id;
337637
337653
  }
337638
337654
  get(id) {
337639
- return this.subs.get(id) || [...this.subs.values()].find((item) => item.name === id || item.qualifiedName === id || item.displayName === id || item.shortId === id || item.natureSlug === natureSlug(id));
337655
+ if (this.subs.has(id)) return this.subs.get(id);
337656
+ const exact = [...this.subs.values()].find((item) => item.id === id || item.qualifiedName === id);
337657
+ if (exact) return exact;
337658
+ return [...this.subs.values()].find((item) => item.name === id || item.displayName === id || item.shortId === id || item.natureSlug === natureSlug(id));
337640
337659
  }
337641
337660
  send(id, prompt) {
337642
337661
  const target = this.get(id);
@@ -337784,6 +337803,7 @@ var SubagentManager = class {
337784
337803
  natureSlug: record.natureSlug,
337785
337804
  displayName: record.displayName,
337786
337805
  qualifiedName: record.qualifiedName,
337806
+ name: record.name,
337787
337807
  createdByAgentId: record.createdByAgentId,
337788
337808
  status: record.status,
337789
337809
  active: record.status !== "closed",
@@ -340412,6 +340432,8 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
340412
340432
  if (name50 === "subagent_close") return agent.handleSubagentCloseEnvelope(args).output;
340413
340433
  if (name50 === "linked_plan") return agent.handleLinkedPlanTool(args);
340414
340434
  if (name50 === "build_history_query") return agent.handleBuildHistoryQuery(args);
340435
+ if (name50 === "context_compress") return (await agent.handleContextCompress(args, signal)).output;
340436
+ if (name50 === "context_history_manage") return agent.handleContextHistoryManage(args).output;
340415
340437
  if (name50 === "question") {
340416
340438
  if (agent.config.getStr("agent", "option_feedback") === "fully_autonomous") return "[question] Disabled by fully_autonomous option feedback.";
340417
340439
  if (!agent.handleQuestion(args)) return "[Question rejected: at least one question with two labeled options is required.]";
@@ -343401,6 +343423,8 @@ var Agent4 = class _Agent {
343401
343423
  continuations = [];
343402
343424
  activeConversationId = "default";
343403
343425
  lastCompression = null;
343426
+ compressionCache = [];
343427
+ nextCompressionCacheId = 1;
343404
343428
  workspaceConversations = /* @__PURE__ */ new Map();
343405
343429
  isSubagentRuntime = false;
343406
343430
  subagentName = "";
@@ -345862,6 +345886,7 @@ Review this persisted peer result and summarize or continue the parent task as n
345862
345886
  this.workspaceConversations.set(key3, {
345863
345887
  chatMessages: [...this.chatMessages],
345864
345888
  history: [...this.history],
345889
+ compressionCache: [...this.compressionCache],
345865
345890
  plan: this.normalizeConversationPlan(this.conversationPlan),
345866
345891
  linkedPlan: this.normalizeLinkedPlan(this.linkedPlan),
345867
345892
  subagentState: this.subagents.serialize(),
@@ -345888,6 +345913,7 @@ Review this persisted peer result and summarize or continue the parent task as n
345888
345913
  title,
345889
345914
  chatMessages: [...this.chatMessages],
345890
345915
  history: [...this.history],
345916
+ compressionCache: [...this.compressionCache],
345891
345917
  plan: this.normalizeConversationPlan(this.conversationPlan),
345892
345918
  linkedPlan: this.normalizeLinkedPlan(this.linkedPlan),
345893
345919
  subagentState: this.subagents.serialize(),
@@ -345915,6 +345941,8 @@ Review this persisted peer result and summarize or continue the parent task as n
345915
345941
  if (!key3) {
345916
345942
  this.chatMessages = [];
345917
345943
  this.history = [];
345944
+ this.compressionCache = [];
345945
+ this.nextCompressionCacheId = 1;
345918
345946
  this.conversationPlan = { items: [] };
345919
345947
  this.linkedPlan = { markdown: "", revision: 0 };
345920
345948
  this.workRuns = [];
@@ -345931,6 +345959,8 @@ Review this persisted peer result and summarize or continue the parent task as n
345931
345959
  const saved = this.workspaceConversations.get(key3);
345932
345960
  if (saved) {
345933
345961
  this.history = [...saved.history];
345962
+ this.compressionCache = saved.compressionCache ? saved.compressionCache.map((entry) => ({ ...entry, messages: [...entry.messages] })) : [];
345963
+ this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map((entry) => Number(entry.id.replace(/^ctx-cache-/, "")) || 0)) + 1;
345934
345964
  this.chatMessages = this.normalizeConversationChatMessages(saved.chatMessages, this.history);
345935
345965
  this.conversationPlan = this.normalizeConversationPlan(saved.plan);
345936
345966
  this.linkedPlan = this.normalizeLinkedPlan(saved.linkedPlan);
@@ -345950,6 +345980,8 @@ Review this persisted peer result and summarize or continue the parent task as n
345950
345980
  const stateKey = this.workspaceConversationStateKey();
345951
345981
  const persisted = stateKey && stored.conversations ? stored.conversations[stateKey] : null;
345952
345982
  this.history = persisted?.history ? [...persisted.history] : [];
345983
+ this.compressionCache = persisted?.compressionCache ? persisted.compressionCache.map((entry) => ({ ...entry, messages: [...entry.messages] })) : [];
345984
+ this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map((entry) => Number(entry.id.replace(/^ctx-cache-/, "")) || 0)) + 1;
345953
345985
  this.chatMessages = this.normalizeConversationChatMessages(persisted?.chatMessages || [], this.history);
345954
345986
  this.conversationPlan = this.normalizeConversationPlan(persisted?.plan);
345955
345987
  this.linkedPlan = this.normalizeLinkedPlan(persisted?.linkedPlan);
@@ -345967,6 +345999,7 @@ Review this persisted peer result and summarize or continue the parent task as n
345967
345999
  this.workspaceConversations.set(key3, {
345968
346000
  chatMessages: [...this.chatMessages],
345969
346001
  history: [...this.history],
346002
+ compressionCache: [...this.compressionCache],
345970
346003
  plan: this.normalizeConversationPlan(this.conversationPlan),
345971
346004
  linkedPlan: this.normalizeLinkedPlan(this.linkedPlan),
345972
346005
  subagentState: this.subagents.serialize(),
@@ -346148,6 +346181,255 @@ Review this persisted peer result and summarize or continue the parent task as n
346148
346181
  truncatedActivities: Math.max(0, publicEvents.length - activities.length)
346149
346182
  });
346150
346183
  }
346184
+ async handleContextCompress(args, signal) {
346185
+ let input = {};
346186
+ try {
346187
+ input = JSON.parse(args || "{}");
346188
+ } catch {
346189
+ }
346190
+ if (this.history.length <= 1) return { ok: false, output: "[context_compress] No context to compress.", error: "No context to compress." };
346191
+ const previousKeepLast = this.config.getNum("context", "keep_recent_messages");
346192
+ const keepRecent = Math.max(2, Math.min(60, Math.floor(Number(input.keep_recent) || previousKeepLast || 10)));
346193
+ const force = Boolean(input.force);
346194
+ try {
346195
+ this.config.set("context", "keep_recent_messages", keepRecent);
346196
+ const msgs = this.history.map((message) => ({ ...message }));
346197
+ const provider = this.engineModel();
346198
+ await this.maybeCompress(msgs, provider, signal, this.activeModelName(), force);
346199
+ if (this.history.length <= 1) return { ok: false, output: "[context_compress] Compression skipped: context unchanged.", error: "Compression skipped." };
346200
+ return {
346201
+ ok: true,
346202
+ output: JSON.stringify({
346203
+ ok: true,
346204
+ compressed: true,
346205
+ at: this.lastCompression?.at,
346206
+ originalMessages: this.lastCompression?.originalMessages,
346207
+ compressedMessages: this.lastCompression?.compressedMessages,
346208
+ originalChars: this.lastCompression?.originalChars,
346209
+ compressedChars: this.lastCompression?.compressedChars,
346210
+ estimatedTokens: this.lastCompression?.compressedTokens,
346211
+ summary: this.lastCompression?.summary?.slice(0, 2e3),
346212
+ model: this.lastCompression?.model,
346213
+ fallback: this.lastCompression?.fallback,
346214
+ displayHistory: { untouched: true, messageCount: this.chatMessages.length }
346215
+ }, null, 2),
346216
+ metadata: { kind: "context-compress" }
346217
+ };
346218
+ } finally {
346219
+ this.config.set("context", "keep_recent_messages", previousKeepLast);
346220
+ }
346221
+ }
346222
+ handleContextHistoryManage(args) {
346223
+ let input = {};
346224
+ try {
346225
+ input = JSON.parse(args || "{}");
346226
+ } catch {
346227
+ }
346228
+ const action = String(input.action || "").trim();
346229
+ if (!action) return { ok: false, output: "[context_history_manage] action is required (list|remove|summarize|restore|search|status).", error: "action is required." };
346230
+ if (action === "list") {
346231
+ const limit = Math.max(5, Math.min(400, Math.floor(Number(input.limit || 200))));
346232
+ const entries = this.history.slice(0, limit).map((message, index) => ({
346233
+ position: index,
346234
+ role: String(message.role || ""),
346235
+ name: String(message.name || ""),
346236
+ chars: typeof message.content === "string" ? message.content.length : JSON.stringify(message.content || "").length,
346237
+ preview: this.compressionHistoryContent(message.content || "").slice(0, 160)
346238
+ }));
346239
+ return {
346240
+ ok: true,
346241
+ output: JSON.stringify({
346242
+ ok: true,
346243
+ action: "list",
346244
+ entryCount: this.history.length,
346245
+ listed: entries.length,
346246
+ entries,
346247
+ displayHistory: { untouched: true, messageCount: this.chatMessages.length }
346248
+ }, null, 2),
346249
+ metadata: { kind: "context-history-list" }
346250
+ };
346251
+ }
346252
+ const protectedZone = this.contextHistoryProtectedZone();
346253
+ if (action === "remove") {
346254
+ const position = Math.floor(Number(input.position));
346255
+ if (!Number.isFinite(position) || position < 0 || position >= this.history.length) {
346256
+ return { ok: false, output: `[context_history_manage] remove position ${position} out of range (0..${this.history.length - 1}).`, error: "remove position out of range." };
346257
+ }
346258
+ if (protectedZone.has(position) && !Boolean(input.dangerous)) {
346259
+ return {
346260
+ ok: false,
346261
+ output: `[context_history_manage] remove position ${position} is protected (recent context tail or the last user message). Pass dangerous: true to override.`,
346262
+ error: "remove position is in the protected context zone."
346263
+ };
346264
+ }
346265
+ const removed = this.history.splice(position, 1)[0];
346266
+ this.saveWorkspaceConversationState(true);
346267
+ return {
346268
+ ok: true,
346269
+ output: JSON.stringify({
346270
+ ok: true,
346271
+ action: "remove",
346272
+ removedPosition: position,
346273
+ removedRole: String(removed?.role || ""),
346274
+ remaining: this.history.length,
346275
+ displayHistory: { untouched: true, messageCount: this.chatMessages.length }
346276
+ }, null, 2),
346277
+ metadata: { kind: "context-history-remove" }
346278
+ };
346279
+ }
346280
+ if (action === "summarize") {
346281
+ const from = Math.max(0, Math.floor(Number(input.position || 0)));
346282
+ const toRaw = Math.floor(Number(input.to ?? input.position));
346283
+ const to = Number.isFinite(toRaw) ? Math.min(this.history.length - 1, Math.max(from, toRaw)) : from;
346284
+ if (from >= this.history.length) return { ok: false, output: `[context_history_manage] summarize position ${from} out of range (0..${this.history.length - 1}).`, error: "summarize position out of range." };
346285
+ if (to - from < 1) return { ok: false, output: "[context_history_manage] summarize requires at least two entries in range.", error: "summarize requires a range of at least two entries." };
346286
+ const protectedHit = this.history.slice(from, to + 1).some((_3, index) => protectedZone.has(from + index));
346287
+ if (protectedHit && !Boolean(input.dangerous)) {
346288
+ return {
346289
+ ok: false,
346290
+ output: "[context_history_manage] summarize range includes protected entries (recent context tail or the last user message). Pass dangerous: true to override.",
346291
+ error: "summarize range overlaps the protected context zone."
346292
+ };
346293
+ }
346294
+ const segment = this.history.slice(from, to + 1);
346295
+ const chars = segment.reduce((sum, message) => sum + (typeof message.content === "string" ? message.content.length : JSON.stringify(message.content || "").length), 0);
346296
+ const summary = this.localCompressionSummary(
346297
+ `Workspace: ${this.workspace.current?.path || this.rootPath}
346298
+ Mode: ${this.modeName()}`,
346299
+ segment.map((message, i4) => `#${i4 + 1} [${String(message.role || "unknown")}${message.name ? ` ${String(message.name)}` : ""}]
346300
+ ${this.compressionHistoryContent(message.content || "")}`).join("\n\n").slice(0, 2e4),
346301
+ segment.length,
346302
+ chars
346303
+ );
346304
+ const replacement = { role: "system", content: `[Context History Summary]
346305
+ ${summary}` };
346306
+ this.history.splice(from, to - from + 1, replacement);
346307
+ this.pushCompressionCacheEntry(`[Context History Summary]
346308
+ ${summary}`, segment, "local-summarize", true);
346309
+ this.saveWorkspaceConversationState(true);
346310
+ return {
346311
+ ok: true,
346312
+ output: JSON.stringify({
346313
+ ok: true,
346314
+ action: "summarize",
346315
+ foldedFrom: from,
346316
+ foldedTo: to,
346317
+ foldedEntries: to - from + 1,
346318
+ foldedChars: chars,
346319
+ remaining: this.history.length,
346320
+ summary: summary.slice(0, 2e3),
346321
+ displayHistory: { untouched: true, messageCount: this.chatMessages.length }
346322
+ }, null, 2),
346323
+ metadata: { kind: "context-history-summarize" }
346324
+ };
346325
+ }
346326
+ if (action === "restore") {
346327
+ const restoreId = String(input.restore_id || "").trim();
346328
+ const entry = this.compressionCache.find((item) => item.id === restoreId);
346329
+ if (!entry) return { ok: false, output: `[context_history_manage] restore unknown restore_id: ${restoreId}.`, error: "restore_id not found." };
346330
+ const summaryHeader = entry.summary.startsWith("[Context Compression") ? "[Context Compression" : "[Context History Summary]";
346331
+ const markerIndex = this.history.findIndex((message) => String(message.role || "") === "system" && String(message.content || "").includes(summaryHeader) && String(message.content || "").includes(entry.summary.slice(0, 200)));
346332
+ if (markerIndex < 0) {
346333
+ return { ok: false, output: "[context_history_manage] restore failed: the folded summary is no longer present in context history (already re-folded or removed).", error: "restore target summary not found in history." };
346334
+ }
346335
+ this.history.splice(markerIndex, 1, ...entry.messages.map((message) => ({ ...message })));
346336
+ this.compressionCache = this.compressionCache.filter((item) => item.id !== entry.id);
346337
+ this.saveWorkspaceConversationState(true);
346338
+ return {
346339
+ ok: true,
346340
+ output: JSON.stringify({
346341
+ ok: true,
346342
+ action: "restore",
346343
+ restoreId: entry.id,
346344
+ restoredEntries: entry.messages.length,
346345
+ restoredChars: entry.foldedChars,
346346
+ cacheRemaining: this.compressionCache.length,
346347
+ displayHistory: { untouched: true, messageCount: this.chatMessages.length }
346348
+ }, null, 2),
346349
+ metadata: { kind: "context-history-restore" }
346350
+ };
346351
+ }
346352
+ if (action === "search") {
346353
+ const query = String(input.query || "").trim().toLowerCase();
346354
+ const limit = Math.max(1, Math.min(200, Math.floor(Number(input.limit || 20))));
346355
+ if (!query) return { ok: false, output: "[context_history_manage] search requires query.", error: "search requires query." };
346356
+ const matches = [];
346357
+ for (const entry of this.compressionCache) {
346358
+ if (matches.length >= limit) break;
346359
+ const hit = { cacheId: entry.id, at: entry.at, summary: entry.summary.slice(0, 500), matches: [] };
346360
+ if (entry.summary.toLowerCase().includes(query)) {
346361
+ hit.matches.push({ index: -1, snippet: this.snippetAround(entry.summary, query) });
346362
+ }
346363
+ entry.messages.forEach((message, index) => {
346364
+ if (matches.length >= limit || hit.matches.length >= 40) return;
346365
+ const content = this.compressionHistoryContent(message.content || message.reasoning_content || "");
346366
+ if (content.toLowerCase().includes(query)) {
346367
+ hit.matches.push({ index, snippet: this.snippetAround(content, query) });
346368
+ }
346369
+ });
346370
+ if (hit.matches.length) matches.push(hit);
346371
+ }
346372
+ return {
346373
+ ok: true,
346374
+ output: JSON.stringify({
346375
+ ok: true,
346376
+ action: "search",
346377
+ query: String(input.query || ""),
346378
+ cacheEntries: this.compressionCache.length,
346379
+ matches,
346380
+ displayHistory: { untouched: true, messageCount: this.chatMessages.length }
346381
+ }, null, 2),
346382
+ metadata: { kind: "context-history-search" }
346383
+ };
346384
+ }
346385
+ if (action === "status") {
346386
+ const budget = this.compressionBudget(this.history);
346387
+ const estimatedTokens = this.estimateContextTokens(this.history);
346388
+ const maxTokens = this.contextMaxTokens();
346389
+ const protectedStartIndex = this.contextHistoryProtectedStartIndex();
346390
+ const lastUserIndex = this.history.map((message) => String(message.role || "")).lastIndexOf("user");
346391
+ return {
346392
+ ok: true,
346393
+ output: JSON.stringify({
346394
+ ok: true,
346395
+ action: "status",
346396
+ historyLength: this.history.length,
346397
+ chatMessages: this.chatMessages.length,
346398
+ estimatedTokens,
346399
+ maxTokens,
346400
+ triggerTokens: budget.triggerTokens,
346401
+ targetTokens: budget.targetTokens,
346402
+ summaryTokens: budget.summaryTokens,
346403
+ usagePercent: maxTokens > 0 ? Math.round(estimatedTokens / maxTokens * 1e3) / 10 : 0,
346404
+ thresholdReached: budget.triggerTokens > 0 && estimatedTokens >= budget.triggerTokens,
346405
+ keepRecentMessages: this.config.getNum("context", "keep_recent_messages") || 10,
346406
+ lastCompression: this.lastCompression ? {
346407
+ at: this.lastCompression.at,
346408
+ originalMessages: this.lastCompression.originalMessages,
346409
+ compressedMessages: this.lastCompression.compressedMessages,
346410
+ compressedTokens: this.lastCompression.compressedTokens,
346411
+ model: this.lastCompression.model,
346412
+ fallback: this.lastCompression.fallback
346413
+ } : null,
346414
+ cache: {
346415
+ entries: this.compressionCache.length,
346416
+ totalFoldedEntries: this.compressionCache.reduce((sum, item) => sum + item.foldedEntries, 0),
346417
+ totalFoldedChars: this.compressionCache.reduce((sum, item) => sum + item.foldedChars, 0),
346418
+ ids: this.compressionCache.map((item) => item.id)
346419
+ },
346420
+ protectedZone: {
346421
+ preserveRecentMessages: this.config.getNum("context", "preserve_recent_messages") || 5,
346422
+ protectedStartIndex,
346423
+ lastUserMessageIndex: lastUserIndex,
346424
+ protectedCount: protectedStartIndex >= 0 ? this.history.length - protectedStartIndex : 0
346425
+ },
346426
+ displayHistory: { untouched: true, messageCount: this.chatMessages.length }
346427
+ }, null, 2),
346428
+ metadata: { kind: "context-history-status" }
346429
+ };
346430
+ }
346431
+ return { ok: false, output: `[context_history_manage] Unknown action: ${action}`, error: `Unknown action: ${action}` };
346432
+ }
346151
346433
  recordContextCompressionStep() {
346152
346434
  const runId = this.currentWorkRunId();
346153
346435
  if (!runId) return;
@@ -346546,6 +346828,7 @@ Review this persisted peer result and summarize or continue the parent task as n
346546
346828
  model: fallbackUsed ? "model-switch-segmented-with-fallback" : modelName,
346547
346829
  fallback: fallbackUsed
346548
346830
  };
346831
+ this.pushCompressionCacheEntry(summary2, originalMessages.slice(0, recentStart), "model-switch", fallbackUsed);
346549
346832
  this.persistCompressedHistory(summary2, recent.length, candidate2);
346550
346833
  this.saveWorkspaceConversationState(true);
346551
346834
  return { compressed: true, rounds, segments, droppedMessages: 0, estimatedTokens: this.estimateContextTokens(candidate2), maxTokens };
@@ -346579,6 +346862,7 @@ Review this persisted peer result and summarize or continue the parent task as n
346579
346862
  model: fallbackUsed ? "model-switch-segmented-with-fallback" : modelName,
346580
346863
  fallback: fallbackUsed || droppedMessages > 0
346581
346864
  };
346865
+ this.pushCompressionCacheEntry(summary, originalMessages.slice(0, recentStart), "model-switch", fallbackUsed || droppedMessages > 0);
346582
346866
  this.persistCompressedHistory(summary, recent.length, candidate);
346583
346867
  this.saveWorkspaceConversationState(true);
346584
346868
  return { compressed: true, rounds, segments, droppedMessages, estimatedTokens: this.estimateContextTokens(candidate), maxTokens };
@@ -348015,7 +348299,7 @@ ${settled?.result || settled?.error || ""}`.trim();
348015
348299
  const transcript = sa.messages.map((m2) => `[${m2.role}] ${m2.content}`).join("\n");
348016
348300
  return this.subagents.toToolResult(
348017
348301
  sa.id,
348018
- `get.subagent("${sa.name}")
348302
+ `get.subagent("${sa.name}", id="${sa.id}")
348019
348303
  Status: ${sa.status}
348020
348304
  Model: ${sa.model}
348021
348305
  Mode: ${sa.agentMode}
@@ -348807,7 +349091,7 @@ Falling back to built-in engine.` }];
348807
349091
  if (!this.config.getBool("context", "auto_compress")) return;
348808
349092
  const total = msgs.reduce((sum, m2) => sum + (typeof m2.content === "string" ? m2.content.length : JSON.stringify(m2.content || "").length), 0);
348809
349093
  const budget = this.compressionBudget(msgs);
348810
- if (budget.estimatedTokens < budget.triggerTokens) return;
349094
+ if (budget.estimatedTokens < budget.triggerTokens && !force) return;
348811
349095
  if (!force && this.lastCompression && String(msgs[0]?.content || "").includes(this.lastCompression.summary)) {
348812
349096
  const baselineChars = Math.max(0, Number(this.lastCompression.compressedChars || 0));
348813
349097
  const baselineTokens = Math.max(0, Number(this.lastCompression.compressedTokens || 0));
@@ -348859,6 +349143,7 @@ Falling back to built-in engine.` }];
348859
349143
  model: compression.model,
348860
349144
  fallback: compression.fallback
348861
349145
  };
349146
+ this.pushCompressionCacheEntry(compression.summary, middle, compression.model, compression.fallback);
348862
349147
  this.persistCompressedHistory(compression.summary, recent.length, msgs);
348863
349148
  }
348864
349149
  async buildCompressionSummary(middle, totalChars, budget, provider, signal, compressionModel, currentInstruction = "") {
@@ -349007,6 +349292,48 @@ ${text.slice(-tailChars).trimStart()}`;
349007
349292
  }
349008
349293
  if (this.isSubagentRuntime) this.subagentContextPersist?.(this.history.map((message) => ({ ...message })), this.lastCompression);
349009
349294
  }
349295
+ pushCompressionCacheEntry(summary, messages, model, fallback) {
349296
+ if (!messages.length) return;
349297
+ const foldedChars = messages.reduce((sum, message) => sum + (typeof message.content === "string" ? message.content.length : JSON.stringify(message.content || "").length), 0);
349298
+ this.compressionCache.push({
349299
+ id: `ctx-cache-${this.nextCompressionCacheId}`,
349300
+ at: (/* @__PURE__ */ new Date()).toISOString(),
349301
+ summary,
349302
+ messages: messages.map((message) => ({ ...message })),
349303
+ foldedEntries: messages.length,
349304
+ foldedChars,
349305
+ model,
349306
+ fallback
349307
+ });
349308
+ this.nextCompressionCacheId += 1;
349309
+ const maxEntries = Math.max(0, Math.floor(this.config.getNum("context", "compression_cache_max") || 8));
349310
+ if (this.compressionCache.length > maxEntries) {
349311
+ this.compressionCache = this.compressionCache.slice(this.compressionCache.length - maxEntries);
349312
+ }
349313
+ this.saveWorkspaceConversationState(true);
349314
+ }
349315
+ contextHistoryProtectedStartIndex() {
349316
+ const preserve = Math.max(0, Math.floor(this.config.getNum("context", "preserve_recent_messages") || 5));
349317
+ const lastUserIndex = this.history.map((message) => String(message.role || "")).lastIndexOf("user");
349318
+ const candidates = [];
349319
+ if (preserve > 0 && this.history.length > 0) candidates.push(Math.max(0, this.history.length - preserve));
349320
+ if (lastUserIndex >= 0) candidates.push(lastUserIndex);
349321
+ return candidates.length ? Math.min(...candidates) : -1;
349322
+ }
349323
+ contextHistoryProtectedZone() {
349324
+ const start = this.contextHistoryProtectedStartIndex();
349325
+ const zone = /* @__PURE__ */ new Set();
349326
+ if (start >= 0) for (let i4 = start; i4 < this.history.length; i4 += 1) zone.add(i4);
349327
+ return zone;
349328
+ }
349329
+ snippetAround(content, query, radius = 150) {
349330
+ const text = String(content || "");
349331
+ const index = text.toLowerCase().indexOf(query.toLowerCase());
349332
+ if (index < 0) return text.slice(0, radius * 2);
349333
+ const from = Math.max(0, index - radius);
349334
+ const to = Math.min(text.length, index + query.length + radius);
349335
+ return `${from > 0 ? "\u2026" : ""}${text.slice(from, to).trim()}${to < text.length ? "\u2026" : ""}`;
349336
+ }
349010
349337
  buildSystemPrompt() {
349011
349338
  const cwd = this.workspace.current?.path || this.rootPath;
349012
349339
  const enabledSkills = this.skills.active();
@@ -58,6 +58,16 @@ export interface AutoRouteRatingResult {
58
58
  }
59
59
  export declare const ROOT_AGENT_ACTOR_ID = "00000000-0000-4000-8000-000000000001";
60
60
  export declare function normalizeIntelligenceTier(value: unknown): 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'ultra';
61
+ export interface CompressionCacheEntry {
62
+ id: string;
63
+ at: string;
64
+ summary: string;
65
+ messages: Array<Record<string, unknown>>;
66
+ foldedEntries: number;
67
+ foldedChars: number;
68
+ model: string;
69
+ fallback: boolean;
70
+ }
61
71
  type ConversationModelSelection = {
62
72
  kind: 'auto';
63
73
  } | {
@@ -286,6 +296,8 @@ export declare class Agent {
286
296
  model: string;
287
297
  fallback: boolean;
288
298
  } | null;
299
+ private compressionCache;
300
+ private nextCompressionCacheId;
289
301
  private workspaceConversations;
290
302
  isSubagentRuntime: boolean;
291
303
  private subagentName;
@@ -590,6 +602,8 @@ export declare class Agent {
590
602
  endedAt?: string;
591
603
  }>;
592
604
  handleBuildHistoryQuery(args: string): string;
605
+ handleContextCompress(args: string, signal?: AbortSignal): Promise<NewmarkToolResult>;
606
+ handleContextHistoryManage(args: string): NewmarkToolResult;
593
607
  recordContextCompressionStep(): void;
594
608
  compressionContinuationPrompt(): string;
595
609
  mirrorConversationStateFrom(id: string, source: Pick<Agent, 'chatMessages' | 'history' | 'conversationPlan'> & Partial<Pick<Agent, 'linkedPlan' | 'subagents' | 'workRuns' | 'continuations'>> & {
@@ -801,6 +815,10 @@ export declare class Agent {
801
815
  private compactSummaryBody;
802
816
  private formatCompressionSummary;
803
817
  private persistCompressedHistory;
818
+ private pushCompressionCacheEntry;
819
+ private contextHistoryProtectedStartIndex;
820
+ private contextHistoryProtectedZone;
821
+ private snippetAround;
804
822
  buildSystemPrompt(): string;
805
823
  /**
806
824
  * dev-0.3.0: assemble the model-request system prompt through the Context