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.
- package/dist/conversation-utility-host.bundle.cjs +338 -11
- package/dist/core/agent.d.ts +18 -0
- package/dist/core/agent.js +317 -2
- package/dist/core/agentKernelRunner.js +4 -0
- package/dist/core/config.js +2 -0
- package/dist/core/subagent.d.ts +1 -0
- package/dist/core/subagent.js +15 -3
- package/dist/core/toolPolicy.js +2 -0
- package/dist/tools/index.js +16 -6
- package/dist/tools/nativeTools.js +2 -0
- package/dist/ui/index.html +15 -4
- package/dist/wsl-agent-host.bundle.cjs +338 -11
- package/package.json +1 -1
|
@@ -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
|
|
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:
|
|
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 '${
|
|
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
|
-
|
|
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();
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -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
|