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
|
@@ -327015,6 +327015,8 @@ var NATIVE_TOOL_CATALOG = [
|
|
|
327015
327015
|
{ name: "subagent_close", label: "Subagent close", description: "Close a same-conversation peer agent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
|
|
327016
327016
|
{ 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" },
|
|
327017
327017
|
{ 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" },
|
|
327018
|
+
{ 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" },
|
|
327019
|
+
{ 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" },
|
|
327018
327020
|
{ name: "question", label: "Ask question", description: "Ask the user for structured option feedback.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
|
|
327019
327021
|
{ name: "skill_download", label: "Skill download", description: "Download and install a skill.", category: "agent", defaultEnabled: true },
|
|
327020
327022
|
{ 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" },
|
|
@@ -327821,6 +327823,8 @@ function defaultConfig() {
|
|
|
327821
327823
|
auto_compress: { _description: "Auto-compress history", _type: "boolean", value: true },
|
|
327822
327824
|
compress_threshold_chars: { _description: "Compression threshold", _type: "integer", value: 8e4 },
|
|
327823
327825
|
keep_recent_messages: { _description: "Keep recent messages", _type: "integer", value: 10 },
|
|
327826
|
+
preserve_recent_messages: { _description: "dev-0.3.8 protected recent-message zone for context_history_manage (0 disables)", _type: "integer", value: 5 },
|
|
327827
|
+
compression_cache_max: { _description: "dev-0.3.8 max folded-segment cache entries retained for restore/search", _type: "integer", value: 8 },
|
|
327824
327828
|
structured_context_v2: { _description: "dev-0.3.0 structured context v2 (orchestrator + fixed order + snapshot)", _type: "boolean", value: true },
|
|
327825
327829
|
build_history_persistence: { _description: "dev-0.3.0 append-only Build History persistence", _type: "boolean", value: true },
|
|
327826
327830
|
branch_log_v2: { _description: "dev-0.3.0 branch long-log v2 (epoch summaries)", _type: "boolean", value: true },
|
|
@@ -334509,6 +334513,8 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
334509
334513
|
"pdf_read",
|
|
334510
334514
|
"linked_plan",
|
|
334511
334515
|
"build_history_query",
|
|
334516
|
+
"context_compress",
|
|
334517
|
+
"context_history_manage",
|
|
334512
334518
|
"question",
|
|
334513
334519
|
"task",
|
|
334514
334520
|
"subagent_list",
|
|
@@ -335526,14 +335532,24 @@ var ToolExecutor = class {
|
|
|
335526
335532
|
remote_root: { type: "string" },
|
|
335527
335533
|
remote_path: { type: "string" }
|
|
335528
335534
|
}, ["action"]),
|
|
335529
|
-
t3("task", "Create a same-conversation peer agent and return immediately. The peer has a
|
|
335530
|
-
t3("subagent_list", "List flat same-conversation peer agents, optionally filtered by status.", { status: { type: "string", enum: ["idle", "queued", "working", "completed", "error", "closed"] } }, []),
|
|
335531
|
-
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." } }, []),
|
|
335532
|
-
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" } }, []),
|
|
335533
|
-
t3("subagent_result", "Return the persisted transcript, mailbox summary, status, and latest result for a peer agent.", { id: { type: "string" }, name: { type: "string" } }, []),
|
|
335534
|
-
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" } }, []),
|
|
335535
|
+
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"]),
|
|
335536
|
+
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"] } }, []),
|
|
335537
|
+
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." } }, []),
|
|
335538
|
+
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" } }, []),
|
|
335539
|
+
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." } }, []),
|
|
335540
|
+
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." } }, []),
|
|
335535
335541
|
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"]),
|
|
335536
335542
|
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." } }, []),
|
|
335543
|
+
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." } }, []),
|
|
335544
|
+
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.", {
|
|
335545
|
+
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." },
|
|
335546
|
+
position: { type: "number", minimum: 0, description: "0-based context entry index for remove, or the start of the range for summarize." },
|
|
335547
|
+
to: { type: "number", minimum: 0, description: "0-based inclusive end of the range for summarize. Defaults to position." },
|
|
335548
|
+
limit: { type: "number", minimum: 5, maximum: 400, description: "Maximum context entries to list (default 200), or search matches to return (default 20)." },
|
|
335549
|
+
restore_id: { type: "string", description: "Cache id of a folded segment (from search or status) to restore into context." },
|
|
335550
|
+
query: { type: "string", description: "Case-insensitive text to search for across cached folded segments and their summaries." },
|
|
335551
|
+
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." }
|
|
335552
|
+
}, ["action"]),
|
|
335537
335553
|
t3("question", "Ask user a multiple-choice question", { questions: { type: "array" } }, ["questions"]),
|
|
335538
335554
|
t3("skill_download", "Download a skill", { name: { type: "string" }, source: { type: "string" } }, ["name", "source"]),
|
|
335539
335555
|
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 } }, []),
|
|
@@ -337619,7 +337635,7 @@ var SubagentManager = class {
|
|
|
337619
337635
|
natureSlug: slug,
|
|
337620
337636
|
displayName,
|
|
337621
337637
|
qualifiedName,
|
|
337622
|
-
name:
|
|
337638
|
+
name: slug,
|
|
337623
337639
|
conversationId: this.conversationId,
|
|
337624
337640
|
createdByAgentId,
|
|
337625
337641
|
prompt,
|
|
@@ -337630,7 +337646,7 @@ var SubagentManager = class {
|
|
|
337630
337646
|
flowName: flowName || void 0,
|
|
337631
337647
|
flowPc: Math.max(0, Math.floor(Number(flowPc) || 0)),
|
|
337632
337648
|
status: "queued",
|
|
337633
|
-
messages: [{ role: "system", content: `Peer agent '${
|
|
337649
|
+
messages: [{ role: "system", content: `Peer agent '${slug}' (${id}): ${prompt}` }, { role: "user", content: prompt, hidden_user_input: true }],
|
|
337634
337650
|
result: null,
|
|
337635
337651
|
createdAt: stamp,
|
|
337636
337652
|
updatedAt: stamp
|
|
@@ -337640,7 +337656,10 @@ var SubagentManager = class {
|
|
|
337640
337656
|
return id;
|
|
337641
337657
|
}
|
|
337642
337658
|
get(id) {
|
|
337643
|
-
|
|
337659
|
+
if (this.subs.has(id)) return this.subs.get(id);
|
|
337660
|
+
const exact = [...this.subs.values()].find((item) => item.id === id || item.qualifiedName === id);
|
|
337661
|
+
if (exact) return exact;
|
|
337662
|
+
return [...this.subs.values()].find((item) => item.name === id || item.displayName === id || item.shortId === id || item.natureSlug === natureSlug(id));
|
|
337644
337663
|
}
|
|
337645
337664
|
send(id, prompt) {
|
|
337646
337665
|
const target = this.get(id);
|
|
@@ -337788,6 +337807,7 @@ var SubagentManager = class {
|
|
|
337788
337807
|
natureSlug: record.natureSlug,
|
|
337789
337808
|
displayName: record.displayName,
|
|
337790
337809
|
qualifiedName: record.qualifiedName,
|
|
337810
|
+
name: record.name,
|
|
337791
337811
|
createdByAgentId: record.createdByAgentId,
|
|
337792
337812
|
status: record.status,
|
|
337793
337813
|
active: record.status !== "closed",
|
|
@@ -340416,6 +340436,8 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
|
|
|
340416
340436
|
if (name50 === "subagent_close") return agent.handleSubagentCloseEnvelope(args).output;
|
|
340417
340437
|
if (name50 === "linked_plan") return agent.handleLinkedPlanTool(args);
|
|
340418
340438
|
if (name50 === "build_history_query") return agent.handleBuildHistoryQuery(args);
|
|
340439
|
+
if (name50 === "context_compress") return (await agent.handleContextCompress(args, signal)).output;
|
|
340440
|
+
if (name50 === "context_history_manage") return agent.handleContextHistoryManage(args).output;
|
|
340419
340441
|
if (name50 === "question") {
|
|
340420
340442
|
if (agent.config.getStr("agent", "option_feedback") === "fully_autonomous") return "[question] Disabled by fully_autonomous option feedback.";
|
|
340421
340443
|
if (!agent.handleQuestion(args)) return "[Question rejected: at least one question with two labeled options is required.]";
|
|
@@ -343405,6 +343427,8 @@ var Agent4 = class _Agent {
|
|
|
343405
343427
|
continuations = [];
|
|
343406
343428
|
activeConversationId = "default";
|
|
343407
343429
|
lastCompression = null;
|
|
343430
|
+
compressionCache = [];
|
|
343431
|
+
nextCompressionCacheId = 1;
|
|
343408
343432
|
workspaceConversations = /* @__PURE__ */ new Map();
|
|
343409
343433
|
isSubagentRuntime = false;
|
|
343410
343434
|
subagentName = "";
|
|
@@ -345866,6 +345890,7 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
345866
345890
|
this.workspaceConversations.set(key3, {
|
|
345867
345891
|
chatMessages: [...this.chatMessages],
|
|
345868
345892
|
history: [...this.history],
|
|
345893
|
+
compressionCache: [...this.compressionCache],
|
|
345869
345894
|
plan: this.normalizeConversationPlan(this.conversationPlan),
|
|
345870
345895
|
linkedPlan: this.normalizeLinkedPlan(this.linkedPlan),
|
|
345871
345896
|
subagentState: this.subagents.serialize(),
|
|
@@ -345892,6 +345917,7 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
345892
345917
|
title,
|
|
345893
345918
|
chatMessages: [...this.chatMessages],
|
|
345894
345919
|
history: [...this.history],
|
|
345920
|
+
compressionCache: [...this.compressionCache],
|
|
345895
345921
|
plan: this.normalizeConversationPlan(this.conversationPlan),
|
|
345896
345922
|
linkedPlan: this.normalizeLinkedPlan(this.linkedPlan),
|
|
345897
345923
|
subagentState: this.subagents.serialize(),
|
|
@@ -345919,6 +345945,8 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
345919
345945
|
if (!key3) {
|
|
345920
345946
|
this.chatMessages = [];
|
|
345921
345947
|
this.history = [];
|
|
345948
|
+
this.compressionCache = [];
|
|
345949
|
+
this.nextCompressionCacheId = 1;
|
|
345922
345950
|
this.conversationPlan = { items: [] };
|
|
345923
345951
|
this.linkedPlan = { markdown: "", revision: 0 };
|
|
345924
345952
|
this.workRuns = [];
|
|
@@ -345935,6 +345963,8 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
345935
345963
|
const saved = this.workspaceConversations.get(key3);
|
|
345936
345964
|
if (saved) {
|
|
345937
345965
|
this.history = [...saved.history];
|
|
345966
|
+
this.compressionCache = saved.compressionCache ? saved.compressionCache.map((entry) => ({ ...entry, messages: [...entry.messages] })) : [];
|
|
345967
|
+
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map((entry) => Number(entry.id.replace(/^ctx-cache-/, "")) || 0)) + 1;
|
|
345938
345968
|
this.chatMessages = this.normalizeConversationChatMessages(saved.chatMessages, this.history);
|
|
345939
345969
|
this.conversationPlan = this.normalizeConversationPlan(saved.plan);
|
|
345940
345970
|
this.linkedPlan = this.normalizeLinkedPlan(saved.linkedPlan);
|
|
@@ -345954,6 +345984,8 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
345954
345984
|
const stateKey = this.workspaceConversationStateKey();
|
|
345955
345985
|
const persisted = stateKey && stored.conversations ? stored.conversations[stateKey] : null;
|
|
345956
345986
|
this.history = persisted?.history ? [...persisted.history] : [];
|
|
345987
|
+
this.compressionCache = persisted?.compressionCache ? persisted.compressionCache.map((entry) => ({ ...entry, messages: [...entry.messages] })) : [];
|
|
345988
|
+
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map((entry) => Number(entry.id.replace(/^ctx-cache-/, "")) || 0)) + 1;
|
|
345957
345989
|
this.chatMessages = this.normalizeConversationChatMessages(persisted?.chatMessages || [], this.history);
|
|
345958
345990
|
this.conversationPlan = this.normalizeConversationPlan(persisted?.plan);
|
|
345959
345991
|
this.linkedPlan = this.normalizeLinkedPlan(persisted?.linkedPlan);
|
|
@@ -345971,6 +346003,7 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
345971
346003
|
this.workspaceConversations.set(key3, {
|
|
345972
346004
|
chatMessages: [...this.chatMessages],
|
|
345973
346005
|
history: [...this.history],
|
|
346006
|
+
compressionCache: [...this.compressionCache],
|
|
345974
346007
|
plan: this.normalizeConversationPlan(this.conversationPlan),
|
|
345975
346008
|
linkedPlan: this.normalizeLinkedPlan(this.linkedPlan),
|
|
345976
346009
|
subagentState: this.subagents.serialize(),
|
|
@@ -346152,6 +346185,255 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
346152
346185
|
truncatedActivities: Math.max(0, publicEvents.length - activities.length)
|
|
346153
346186
|
});
|
|
346154
346187
|
}
|
|
346188
|
+
async handleContextCompress(args, signal) {
|
|
346189
|
+
let input2 = {};
|
|
346190
|
+
try {
|
|
346191
|
+
input2 = JSON.parse(args || "{}");
|
|
346192
|
+
} catch {
|
|
346193
|
+
}
|
|
346194
|
+
if (this.history.length <= 1) return { ok: false, output: "[context_compress] No context to compress.", error: "No context to compress." };
|
|
346195
|
+
const previousKeepLast = this.config.getNum("context", "keep_recent_messages");
|
|
346196
|
+
const keepRecent = Math.max(2, Math.min(60, Math.floor(Number(input2.keep_recent) || previousKeepLast || 10)));
|
|
346197
|
+
const force = Boolean(input2.force);
|
|
346198
|
+
try {
|
|
346199
|
+
this.config.set("context", "keep_recent_messages", keepRecent);
|
|
346200
|
+
const msgs = this.history.map((message) => ({ ...message }));
|
|
346201
|
+
const provider = this.engineModel();
|
|
346202
|
+
await this.maybeCompress(msgs, provider, signal, this.activeModelName(), force);
|
|
346203
|
+
if (this.history.length <= 1) return { ok: false, output: "[context_compress] Compression skipped: context unchanged.", error: "Compression skipped." };
|
|
346204
|
+
return {
|
|
346205
|
+
ok: true,
|
|
346206
|
+
output: JSON.stringify({
|
|
346207
|
+
ok: true,
|
|
346208
|
+
compressed: true,
|
|
346209
|
+
at: this.lastCompression?.at,
|
|
346210
|
+
originalMessages: this.lastCompression?.originalMessages,
|
|
346211
|
+
compressedMessages: this.lastCompression?.compressedMessages,
|
|
346212
|
+
originalChars: this.lastCompression?.originalChars,
|
|
346213
|
+
compressedChars: this.lastCompression?.compressedChars,
|
|
346214
|
+
estimatedTokens: this.lastCompression?.compressedTokens,
|
|
346215
|
+
summary: this.lastCompression?.summary?.slice(0, 2e3),
|
|
346216
|
+
model: this.lastCompression?.model,
|
|
346217
|
+
fallback: this.lastCompression?.fallback,
|
|
346218
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length }
|
|
346219
|
+
}, null, 2),
|
|
346220
|
+
metadata: { kind: "context-compress" }
|
|
346221
|
+
};
|
|
346222
|
+
} finally {
|
|
346223
|
+
this.config.set("context", "keep_recent_messages", previousKeepLast);
|
|
346224
|
+
}
|
|
346225
|
+
}
|
|
346226
|
+
handleContextHistoryManage(args) {
|
|
346227
|
+
let input2 = {};
|
|
346228
|
+
try {
|
|
346229
|
+
input2 = JSON.parse(args || "{}");
|
|
346230
|
+
} catch {
|
|
346231
|
+
}
|
|
346232
|
+
const action = String(input2.action || "").trim();
|
|
346233
|
+
if (!action) return { ok: false, output: "[context_history_manage] action is required (list|remove|summarize|restore|search|status).", error: "action is required." };
|
|
346234
|
+
if (action === "list") {
|
|
346235
|
+
const limit = Math.max(5, Math.min(400, Math.floor(Number(input2.limit || 200))));
|
|
346236
|
+
const entries = this.history.slice(0, limit).map((message, index) => ({
|
|
346237
|
+
position: index,
|
|
346238
|
+
role: String(message.role || ""),
|
|
346239
|
+
name: String(message.name || ""),
|
|
346240
|
+
chars: typeof message.content === "string" ? message.content.length : JSON.stringify(message.content || "").length,
|
|
346241
|
+
preview: this.compressionHistoryContent(message.content || "").slice(0, 160)
|
|
346242
|
+
}));
|
|
346243
|
+
return {
|
|
346244
|
+
ok: true,
|
|
346245
|
+
output: JSON.stringify({
|
|
346246
|
+
ok: true,
|
|
346247
|
+
action: "list",
|
|
346248
|
+
entryCount: this.history.length,
|
|
346249
|
+
listed: entries.length,
|
|
346250
|
+
entries,
|
|
346251
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length }
|
|
346252
|
+
}, null, 2),
|
|
346253
|
+
metadata: { kind: "context-history-list" }
|
|
346254
|
+
};
|
|
346255
|
+
}
|
|
346256
|
+
const protectedZone = this.contextHistoryProtectedZone();
|
|
346257
|
+
if (action === "remove") {
|
|
346258
|
+
const position = Math.floor(Number(input2.position));
|
|
346259
|
+
if (!Number.isFinite(position) || position < 0 || position >= this.history.length) {
|
|
346260
|
+
return { ok: false, output: `[context_history_manage] remove position ${position} out of range (0..${this.history.length - 1}).`, error: "remove position out of range." };
|
|
346261
|
+
}
|
|
346262
|
+
if (protectedZone.has(position) && !Boolean(input2.dangerous)) {
|
|
346263
|
+
return {
|
|
346264
|
+
ok: false,
|
|
346265
|
+
output: `[context_history_manage] remove position ${position} is protected (recent context tail or the last user message). Pass dangerous: true to override.`,
|
|
346266
|
+
error: "remove position is in the protected context zone."
|
|
346267
|
+
};
|
|
346268
|
+
}
|
|
346269
|
+
const removed = this.history.splice(position, 1)[0];
|
|
346270
|
+
this.saveWorkspaceConversationState(true);
|
|
346271
|
+
return {
|
|
346272
|
+
ok: true,
|
|
346273
|
+
output: JSON.stringify({
|
|
346274
|
+
ok: true,
|
|
346275
|
+
action: "remove",
|
|
346276
|
+
removedPosition: position,
|
|
346277
|
+
removedRole: String(removed?.role || ""),
|
|
346278
|
+
remaining: this.history.length,
|
|
346279
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length }
|
|
346280
|
+
}, null, 2),
|
|
346281
|
+
metadata: { kind: "context-history-remove" }
|
|
346282
|
+
};
|
|
346283
|
+
}
|
|
346284
|
+
if (action === "summarize") {
|
|
346285
|
+
const from = Math.max(0, Math.floor(Number(input2.position || 0)));
|
|
346286
|
+
const toRaw = Math.floor(Number(input2.to ?? input2.position));
|
|
346287
|
+
const to = Number.isFinite(toRaw) ? Math.min(this.history.length - 1, Math.max(from, toRaw)) : from;
|
|
346288
|
+
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." };
|
|
346289
|
+
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." };
|
|
346290
|
+
const protectedHit = this.history.slice(from, to + 1).some((_3, index) => protectedZone.has(from + index));
|
|
346291
|
+
if (protectedHit && !Boolean(input2.dangerous)) {
|
|
346292
|
+
return {
|
|
346293
|
+
ok: false,
|
|
346294
|
+
output: "[context_history_manage] summarize range includes protected entries (recent context tail or the last user message). Pass dangerous: true to override.",
|
|
346295
|
+
error: "summarize range overlaps the protected context zone."
|
|
346296
|
+
};
|
|
346297
|
+
}
|
|
346298
|
+
const segment = this.history.slice(from, to + 1);
|
|
346299
|
+
const chars = segment.reduce((sum, message) => sum + (typeof message.content === "string" ? message.content.length : JSON.stringify(message.content || "").length), 0);
|
|
346300
|
+
const summary = this.localCompressionSummary(
|
|
346301
|
+
`Workspace: ${this.workspace.current?.path || this.rootPath}
|
|
346302
|
+
Mode: ${this.modeName()}`,
|
|
346303
|
+
segment.map((message, i4) => `#${i4 + 1} [${String(message.role || "unknown")}${message.name ? ` ${String(message.name)}` : ""}]
|
|
346304
|
+
${this.compressionHistoryContent(message.content || "")}`).join("\n\n").slice(0, 2e4),
|
|
346305
|
+
segment.length,
|
|
346306
|
+
chars
|
|
346307
|
+
);
|
|
346308
|
+
const replacement = { role: "system", content: `[Context History Summary]
|
|
346309
|
+
${summary}` };
|
|
346310
|
+
this.history.splice(from, to - from + 1, replacement);
|
|
346311
|
+
this.pushCompressionCacheEntry(`[Context History Summary]
|
|
346312
|
+
${summary}`, segment, "local-summarize", true);
|
|
346313
|
+
this.saveWorkspaceConversationState(true);
|
|
346314
|
+
return {
|
|
346315
|
+
ok: true,
|
|
346316
|
+
output: JSON.stringify({
|
|
346317
|
+
ok: true,
|
|
346318
|
+
action: "summarize",
|
|
346319
|
+
foldedFrom: from,
|
|
346320
|
+
foldedTo: to,
|
|
346321
|
+
foldedEntries: to - from + 1,
|
|
346322
|
+
foldedChars: chars,
|
|
346323
|
+
remaining: this.history.length,
|
|
346324
|
+
summary: summary.slice(0, 2e3),
|
|
346325
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length }
|
|
346326
|
+
}, null, 2),
|
|
346327
|
+
metadata: { kind: "context-history-summarize" }
|
|
346328
|
+
};
|
|
346329
|
+
}
|
|
346330
|
+
if (action === "restore") {
|
|
346331
|
+
const restoreId = String(input2.restore_id || "").trim();
|
|
346332
|
+
const entry = this.compressionCache.find((item) => item.id === restoreId);
|
|
346333
|
+
if (!entry) return { ok: false, output: `[context_history_manage] restore unknown restore_id: ${restoreId}.`, error: "restore_id not found." };
|
|
346334
|
+
const summaryHeader = entry.summary.startsWith("[Context Compression") ? "[Context Compression" : "[Context History Summary]";
|
|
346335
|
+
const markerIndex = this.history.findIndex((message) => String(message.role || "") === "system" && String(message.content || "").includes(summaryHeader) && String(message.content || "").includes(entry.summary.slice(0, 200)));
|
|
346336
|
+
if (markerIndex < 0) {
|
|
346337
|
+
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." };
|
|
346338
|
+
}
|
|
346339
|
+
this.history.splice(markerIndex, 1, ...entry.messages.map((message) => ({ ...message })));
|
|
346340
|
+
this.compressionCache = this.compressionCache.filter((item) => item.id !== entry.id);
|
|
346341
|
+
this.saveWorkspaceConversationState(true);
|
|
346342
|
+
return {
|
|
346343
|
+
ok: true,
|
|
346344
|
+
output: JSON.stringify({
|
|
346345
|
+
ok: true,
|
|
346346
|
+
action: "restore",
|
|
346347
|
+
restoreId: entry.id,
|
|
346348
|
+
restoredEntries: entry.messages.length,
|
|
346349
|
+
restoredChars: entry.foldedChars,
|
|
346350
|
+
cacheRemaining: this.compressionCache.length,
|
|
346351
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length }
|
|
346352
|
+
}, null, 2),
|
|
346353
|
+
metadata: { kind: "context-history-restore" }
|
|
346354
|
+
};
|
|
346355
|
+
}
|
|
346356
|
+
if (action === "search") {
|
|
346357
|
+
const query = String(input2.query || "").trim().toLowerCase();
|
|
346358
|
+
const limit = Math.max(1, Math.min(200, Math.floor(Number(input2.limit || 20))));
|
|
346359
|
+
if (!query) return { ok: false, output: "[context_history_manage] search requires query.", error: "search requires query." };
|
|
346360
|
+
const matches = [];
|
|
346361
|
+
for (const entry of this.compressionCache) {
|
|
346362
|
+
if (matches.length >= limit) break;
|
|
346363
|
+
const hit = { cacheId: entry.id, at: entry.at, summary: entry.summary.slice(0, 500), matches: [] };
|
|
346364
|
+
if (entry.summary.toLowerCase().includes(query)) {
|
|
346365
|
+
hit.matches.push({ index: -1, snippet: this.snippetAround(entry.summary, query) });
|
|
346366
|
+
}
|
|
346367
|
+
entry.messages.forEach((message, index) => {
|
|
346368
|
+
if (matches.length >= limit || hit.matches.length >= 40) return;
|
|
346369
|
+
const content = this.compressionHistoryContent(message.content || message.reasoning_content || "");
|
|
346370
|
+
if (content.toLowerCase().includes(query)) {
|
|
346371
|
+
hit.matches.push({ index, snippet: this.snippetAround(content, query) });
|
|
346372
|
+
}
|
|
346373
|
+
});
|
|
346374
|
+
if (hit.matches.length) matches.push(hit);
|
|
346375
|
+
}
|
|
346376
|
+
return {
|
|
346377
|
+
ok: true,
|
|
346378
|
+
output: JSON.stringify({
|
|
346379
|
+
ok: true,
|
|
346380
|
+
action: "search",
|
|
346381
|
+
query: String(input2.query || ""),
|
|
346382
|
+
cacheEntries: this.compressionCache.length,
|
|
346383
|
+
matches,
|
|
346384
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length }
|
|
346385
|
+
}, null, 2),
|
|
346386
|
+
metadata: { kind: "context-history-search" }
|
|
346387
|
+
};
|
|
346388
|
+
}
|
|
346389
|
+
if (action === "status") {
|
|
346390
|
+
const budget = this.compressionBudget(this.history);
|
|
346391
|
+
const estimatedTokens = this.estimateContextTokens(this.history);
|
|
346392
|
+
const maxTokens = this.contextMaxTokens();
|
|
346393
|
+
const protectedStartIndex = this.contextHistoryProtectedStartIndex();
|
|
346394
|
+
const lastUserIndex = this.history.map((message) => String(message.role || "")).lastIndexOf("user");
|
|
346395
|
+
return {
|
|
346396
|
+
ok: true,
|
|
346397
|
+
output: JSON.stringify({
|
|
346398
|
+
ok: true,
|
|
346399
|
+
action: "status",
|
|
346400
|
+
historyLength: this.history.length,
|
|
346401
|
+
chatMessages: this.chatMessages.length,
|
|
346402
|
+
estimatedTokens,
|
|
346403
|
+
maxTokens,
|
|
346404
|
+
triggerTokens: budget.triggerTokens,
|
|
346405
|
+
targetTokens: budget.targetTokens,
|
|
346406
|
+
summaryTokens: budget.summaryTokens,
|
|
346407
|
+
usagePercent: maxTokens > 0 ? Math.round(estimatedTokens / maxTokens * 1e3) / 10 : 0,
|
|
346408
|
+
thresholdReached: budget.triggerTokens > 0 && estimatedTokens >= budget.triggerTokens,
|
|
346409
|
+
keepRecentMessages: this.config.getNum("context", "keep_recent_messages") || 10,
|
|
346410
|
+
lastCompression: this.lastCompression ? {
|
|
346411
|
+
at: this.lastCompression.at,
|
|
346412
|
+
originalMessages: this.lastCompression.originalMessages,
|
|
346413
|
+
compressedMessages: this.lastCompression.compressedMessages,
|
|
346414
|
+
compressedTokens: this.lastCompression.compressedTokens,
|
|
346415
|
+
model: this.lastCompression.model,
|
|
346416
|
+
fallback: this.lastCompression.fallback
|
|
346417
|
+
} : null,
|
|
346418
|
+
cache: {
|
|
346419
|
+
entries: this.compressionCache.length,
|
|
346420
|
+
totalFoldedEntries: this.compressionCache.reduce((sum, item) => sum + item.foldedEntries, 0),
|
|
346421
|
+
totalFoldedChars: this.compressionCache.reduce((sum, item) => sum + item.foldedChars, 0),
|
|
346422
|
+
ids: this.compressionCache.map((item) => item.id)
|
|
346423
|
+
},
|
|
346424
|
+
protectedZone: {
|
|
346425
|
+
preserveRecentMessages: this.config.getNum("context", "preserve_recent_messages") || 5,
|
|
346426
|
+
protectedStartIndex,
|
|
346427
|
+
lastUserMessageIndex: lastUserIndex,
|
|
346428
|
+
protectedCount: protectedStartIndex >= 0 ? this.history.length - protectedStartIndex : 0
|
|
346429
|
+
},
|
|
346430
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length }
|
|
346431
|
+
}, null, 2),
|
|
346432
|
+
metadata: { kind: "context-history-status" }
|
|
346433
|
+
};
|
|
346434
|
+
}
|
|
346435
|
+
return { ok: false, output: `[context_history_manage] Unknown action: ${action}`, error: `Unknown action: ${action}` };
|
|
346436
|
+
}
|
|
346155
346437
|
recordContextCompressionStep() {
|
|
346156
346438
|
const runId = this.currentWorkRunId();
|
|
346157
346439
|
if (!runId) return;
|
|
@@ -346550,6 +346832,7 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
346550
346832
|
model: fallbackUsed ? "model-switch-segmented-with-fallback" : modelName,
|
|
346551
346833
|
fallback: fallbackUsed
|
|
346552
346834
|
};
|
|
346835
|
+
this.pushCompressionCacheEntry(summary2, originalMessages.slice(0, recentStart), "model-switch", fallbackUsed);
|
|
346553
346836
|
this.persistCompressedHistory(summary2, recent.length, candidate2);
|
|
346554
346837
|
this.saveWorkspaceConversationState(true);
|
|
346555
346838
|
return { compressed: true, rounds, segments, droppedMessages: 0, estimatedTokens: this.estimateContextTokens(candidate2), maxTokens };
|
|
@@ -346583,6 +346866,7 @@ Review this persisted peer result and summarize or continue the parent task as n
|
|
|
346583
346866
|
model: fallbackUsed ? "model-switch-segmented-with-fallback" : modelName,
|
|
346584
346867
|
fallback: fallbackUsed || droppedMessages > 0
|
|
346585
346868
|
};
|
|
346869
|
+
this.pushCompressionCacheEntry(summary, originalMessages.slice(0, recentStart), "model-switch", fallbackUsed || droppedMessages > 0);
|
|
346586
346870
|
this.persistCompressedHistory(summary, recent.length, candidate);
|
|
346587
346871
|
this.saveWorkspaceConversationState(true);
|
|
346588
346872
|
return { compressed: true, rounds, segments, droppedMessages, estimatedTokens: this.estimateContextTokens(candidate), maxTokens };
|
|
@@ -348019,7 +348303,7 @@ ${settled?.result || settled?.error || ""}`.trim();
|
|
|
348019
348303
|
const transcript = sa.messages.map((m2) => `[${m2.role}] ${m2.content}`).join("\n");
|
|
348020
348304
|
return this.subagents.toToolResult(
|
|
348021
348305
|
sa.id,
|
|
348022
|
-
`get.subagent("${sa.name}")
|
|
348306
|
+
`get.subagent("${sa.name}", id="${sa.id}")
|
|
348023
348307
|
Status: ${sa.status}
|
|
348024
348308
|
Model: ${sa.model}
|
|
348025
348309
|
Mode: ${sa.agentMode}
|
|
@@ -348811,7 +349095,7 @@ Falling back to built-in engine.` }];
|
|
|
348811
349095
|
if (!this.config.getBool("context", "auto_compress")) return;
|
|
348812
349096
|
const total = msgs.reduce((sum, m2) => sum + (typeof m2.content === "string" ? m2.content.length : JSON.stringify(m2.content || "").length), 0);
|
|
348813
349097
|
const budget = this.compressionBudget(msgs);
|
|
348814
|
-
if (budget.estimatedTokens < budget.triggerTokens) return;
|
|
349098
|
+
if (budget.estimatedTokens < budget.triggerTokens && !force) return;
|
|
348815
349099
|
if (!force && this.lastCompression && String(msgs[0]?.content || "").includes(this.lastCompression.summary)) {
|
|
348816
349100
|
const baselineChars = Math.max(0, Number(this.lastCompression.compressedChars || 0));
|
|
348817
349101
|
const baselineTokens = Math.max(0, Number(this.lastCompression.compressedTokens || 0));
|
|
@@ -348863,6 +349147,7 @@ Falling back to built-in engine.` }];
|
|
|
348863
349147
|
model: compression.model,
|
|
348864
349148
|
fallback: compression.fallback
|
|
348865
349149
|
};
|
|
349150
|
+
this.pushCompressionCacheEntry(compression.summary, middle, compression.model, compression.fallback);
|
|
348866
349151
|
this.persistCompressedHistory(compression.summary, recent.length, msgs);
|
|
348867
349152
|
}
|
|
348868
349153
|
async buildCompressionSummary(middle, totalChars, budget, provider, signal, compressionModel, currentInstruction = "") {
|
|
@@ -349011,6 +349296,48 @@ ${text.slice(-tailChars).trimStart()}`;
|
|
|
349011
349296
|
}
|
|
349012
349297
|
if (this.isSubagentRuntime) this.subagentContextPersist?.(this.history.map((message) => ({ ...message })), this.lastCompression);
|
|
349013
349298
|
}
|
|
349299
|
+
pushCompressionCacheEntry(summary, messages, model, fallback) {
|
|
349300
|
+
if (!messages.length) return;
|
|
349301
|
+
const foldedChars = messages.reduce((sum, message) => sum + (typeof message.content === "string" ? message.content.length : JSON.stringify(message.content || "").length), 0);
|
|
349302
|
+
this.compressionCache.push({
|
|
349303
|
+
id: `ctx-cache-${this.nextCompressionCacheId}`,
|
|
349304
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
349305
|
+
summary,
|
|
349306
|
+
messages: messages.map((message) => ({ ...message })),
|
|
349307
|
+
foldedEntries: messages.length,
|
|
349308
|
+
foldedChars,
|
|
349309
|
+
model,
|
|
349310
|
+
fallback
|
|
349311
|
+
});
|
|
349312
|
+
this.nextCompressionCacheId += 1;
|
|
349313
|
+
const maxEntries = Math.max(0, Math.floor(this.config.getNum("context", "compression_cache_max") || 8));
|
|
349314
|
+
if (this.compressionCache.length > maxEntries) {
|
|
349315
|
+
this.compressionCache = this.compressionCache.slice(this.compressionCache.length - maxEntries);
|
|
349316
|
+
}
|
|
349317
|
+
this.saveWorkspaceConversationState(true);
|
|
349318
|
+
}
|
|
349319
|
+
contextHistoryProtectedStartIndex() {
|
|
349320
|
+
const preserve = Math.max(0, Math.floor(this.config.getNum("context", "preserve_recent_messages") || 5));
|
|
349321
|
+
const lastUserIndex = this.history.map((message) => String(message.role || "")).lastIndexOf("user");
|
|
349322
|
+
const candidates = [];
|
|
349323
|
+
if (preserve > 0 && this.history.length > 0) candidates.push(Math.max(0, this.history.length - preserve));
|
|
349324
|
+
if (lastUserIndex >= 0) candidates.push(lastUserIndex);
|
|
349325
|
+
return candidates.length ? Math.min(...candidates) : -1;
|
|
349326
|
+
}
|
|
349327
|
+
contextHistoryProtectedZone() {
|
|
349328
|
+
const start = this.contextHistoryProtectedStartIndex();
|
|
349329
|
+
const zone = /* @__PURE__ */ new Set();
|
|
349330
|
+
if (start >= 0) for (let i4 = start; i4 < this.history.length; i4 += 1) zone.add(i4);
|
|
349331
|
+
return zone;
|
|
349332
|
+
}
|
|
349333
|
+
snippetAround(content, query, radius = 150) {
|
|
349334
|
+
const text = String(content || "");
|
|
349335
|
+
const index = text.toLowerCase().indexOf(query.toLowerCase());
|
|
349336
|
+
if (index < 0) return text.slice(0, radius * 2);
|
|
349337
|
+
const from = Math.max(0, index - radius);
|
|
349338
|
+
const to = Math.min(text.length, index + query.length + radius);
|
|
349339
|
+
return `${from > 0 ? "\u2026" : ""}${text.slice(from, to).trim()}${to < text.length ? "\u2026" : ""}`;
|
|
349340
|
+
}
|
|
349014
349341
|
buildSystemPrompt() {
|
|
349015
349342
|
const cwd = this.workspace.current?.path || this.rootPath;
|
|
349016
349343
|
const enabledSkills = this.skills.active();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newmark-agent",
|
|
3
3
|
"productName": "Newmark Agent",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.8",
|
|
5
5
|
"description": "Newmark Agent — Portable AI coding agent with rich GUI and CLI (TypeScript)",
|
|
6
6
|
"homepage": "https://github.com/positer/Newmark-Agent",
|
|
7
7
|
"repository": {
|