Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ export function createChatMessageTransformHandler(

stripHallucinations(output.messages)
cacheSystemPromptTokens(state, output.messages)
assignMessageRefs(state, output.messages)
const assigned = assignMessageRefs(state, output.messages)
if (assigned > 0) {
await saveSessionState(state, logger)
}
syncCompressionBlocks(state, logger, output.messages)
syncToolCache(state, config, logger, output.messages)
buildToolIdList(state, output.messages)
Expand Down
18 changes: 18 additions & 0 deletions lib/messages/inject/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ export const injectCompressNudges = (
state.nudges.contextLimitAnchors.clear()
state.nudges.turnNudgeAnchors.clear()
state.nudges.iterationNudgeAnchors.clear()

const continuationReminder =
"\n<dcp-system-reminder>Compression complete. Resume your previous task from where you left off.</dcp-system-reminder>"
if (lastMessage) {
const targetMessage = lastMessage.message
let injected = false
for (const part of targetMessage.parts) {
if (part.type === "text" && typeof part.text === "string") {
part.text += continuationReminder
injected = true
break
}
}
if (!injected) {
targetMessage.parts.push(createSyntheticTextPart(targetMessage, continuationReminder))
}
}

void saveSessionState(state, logger)
return
}
Expand Down
64 changes: 32 additions & 32 deletions lib/prompts/compress-message.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
export const COMPRESS_MESSAGE = `Collapse selected individual messages in the conversation into detailed summaries.
export const COMPRESS_MESSAGE = `将对话中选定的单条消息折叠为详细摘要。

THE SUMMARY
Your summary must be EXHAUSTIVE. Capture file paths, function signatures, decisions made, constraints discovered, key findings, tool outcomes, and user intent details that matter... EVERYTHING that preserves the value of the selected message after the raw message is removed.
摘要要求
摘要必须详尽。捕获文件路径、函数签名、所做决策、发现的约束、关键发现、工具结果,以及重要的用户意图细节……所有在原始消息被移除后保留其价值的内容。

USER INTENT FIDELITY
When a selected message contains user intent, preserve that intent with extra care. Do not change scope, constraints, priorities, acceptance criteria, or requested outcomes.
Directly quote short user instructions when that best preserves exact meaning.
用户意图保真
当选定消息包含用户意图时,格外小心地保留该意图。不要改变范围、约束、优先级、验收标准或请求的结果。
当直接引用简短用户指令最能保留精确含义时,直接引用。

Yet be LEAN. Strip away the noise: failed attempts that led nowhere, verbose tool output, and repetition. What remains should be pure signal - golden nuggets of detail that preserve full understanding with zero ambiguity.
If a message contains no significant technical decisions, code changes, or user requirements, produce a minimal one-line summary rather than a detailed one.
同时保持精简。去除噪音:无果的失败尝试、冗长的工具输出和重复。留下的应该是纯信号——保留完整理解、零歧义的精华细节。
如果消息不包含重要的技术决策、代码变更或用户需求,生成最小化的一行摘要而非详细摘要。

MESSAGE IDS
You specify individual raw messages by ID using the injected IDs visible in the conversation:
消息 ID
使用对话中可见的注入 ID 按 ID 指定单条原始消息:

- \`mNNNN\` IDs identify raw messages
- \`mNNNN\` ID 标识原始消息

Each message has an ID inside XML metadata tags like \`<dcp-message-id priority="high">m0007</dcp-message-id>\`.
The same ID tag appears in every tool output of the message it belongs to — each unique ID identifies one complete message.
Treat these tags as message metadata only, not as content to summarize. Use only the inner \`mNNNN\` value as the \`messageId\`.
The \`priority\` attribute indicates relative context cost. You MUST compress high-priority messages when their full text is no longer necessary for the active task.
If prior compress-tool results are present, always compress and summarize them minimally only as part of a broader compression pass. Do not invoke the compress tool solely to re-compress an earlier compression result.
Messages marked as \`<dcp-message-id>BLOCKED</dcp-message-id>\` cannot be compressed.
每条消息在 XML 元数据标签内有 ID,如 \`\`。
相同的 ID 标签出现在该消息所属的每个工具输出中——每个唯一 ID 标识一条完整消息。
将这些标签仅视为消息元数据,而非要摘要的内容。只使用内部的 \`mNNNN\` 值作为 \`messageId\`
\`priority\` 属性表示相对上下文成本。当高优先级消息的完整文本对当前任务不再必要时,你必须压缩它们。
如果存在之前的压缩工具结果,始终在更广泛的压缩过程中将它们最小化地压缩和摘要。不要仅为了重新压缩之前的压缩结果而调用压缩工具。
标记为 \`\` 的消息不能被压缩。

Rules:
规则:

- Pick each \`messageId\` directly from injected IDs visible in context.
- Only use raw message IDs of the form \`mNNNN\`.
- Ignore XML attributes such as \`priority\` when copying the ID; use only the inner \`mNNNN\` value.
- Do not invent IDs. Use only IDs that are present in context.
- 直接从上下文中可见的注入 ID 选取每个 \`messageId\`
- 只使用 \`mNNNN\` 形式的原始消息 ID。
- 复制 ID 时忽略 \`priority\` 等 XML 属性;只使用内部的 \`mNNNN\` 值。
- 不要发明 ID。只使用上下文中存在的 ID。

BATCHING
Select MANY messages in a single tool call when they are safe to compress.
Each entry should summarize exactly one message, and the tool can receive as many entries as needed in one batch.
批量处理
当多条消息可以安全压缩时,在单次工具调用中选择多条消息。
每个条目应恰好摘要一条消息,工具可以在一次批量中接收任意数量的条目。

GENERAL CLEANUP
Use the topic "general cleanup" for broad cleanup passes.
During general cleanup, compress all medium and high-priority messages that are not relevant to the active task.
Optimize for reducing context footprint, not for grouping messages by topic.
Do not compress away still-active instructions, unresolved questions, or constraints that are likely to matter soon.
Prioritize the earliest messages in the context as they will be the least relevant to the active task.
General cleanup should be done periodically between other normal compression tool passes, not as the primary form of compression.
通用清理
使用主题"通用清理"进行广泛的清理。
在通用清理期间,压缩所有与当前任务无关的中高优先级消息。
优化减少上下文占用,而非按主题分组消息。
不要压缩掉仍然活跃的指令、未解决的问题或可能很快重要的约束。
优先处理上下文中最早的消息,因为它们与当前任务的相关性最低。
通用清理应定期在其他正常压缩工具调用之间进行,而非作为压缩的主要形式。
`
91 changes: 52 additions & 39 deletions lib/prompts/compress-range.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,73 @@
export const COMPRESS_RANGE = `Collapse a range in the conversation into a detailed summary.
export const COMPRESS_RANGE = `将对话中的一个范围折叠为详细摘要。

THE SUMMARY
Your summary must be EXHAUSTIVE. Capture file paths, function signatures, decisions made, constraints discovered, key findings... EVERYTHING that maintains context integrity. This is not a brief note - it is an authoritative record so faithful that the original conversation adds no value.
输出格式
每个摘要必须使用 Markdown 标题结构:

USER INTENT FIDELITY
When the compressed range includes user messages, preserve the user's intent with extra care. Do not change scope, constraints, priorities, acceptance criteria, or requested outcomes.
Directly quote user messages when they are short enough to include safely. Direct quotes are preferred when they best preserve exact meaning.
\`\`\`
## 分析
[分析:探索了什么、遇到了什么问题、尝试了哪些方法]

Yet be LEAN. Strip away the noise: failed attempts that led nowhere, verbose tool outputs, back-and-forth exploration. What remains should be pure signal - golden nuggets of detail that preserve full understanding with zero ambiguity.
## 总结
[总结:最终结论、关键决策、重要文件路径、函数签名、代码变更]
\`\`\`

COMPRESSED BLOCK PLACEHOLDERS
When the selected range includes previously compressed blocks, use this exact placeholder format when referencing one:
禁止使用 \`<analysis>\` 或 \`<summary>\` 等 XML 标签。只使用 Markdown \`## 分析\` 和 \`## 总结\` 标题。

摘要要求
摘要必须详尽。捕获文件路径、函数签名、所做决策、发现的约束、关键发现……所有维持上下文完整性的内容。这不是简短笔记——而是一份权威记录,忠实到原始对话不再有任何价值。

用户意图保真
当压缩范围包含用户消息时,格外小心地保留用户意图。不要改变范围、约束、优先级、验收标准或请求的结果。
当用户消息足够短时直接引用。直接引用在最能保留精确含义时优先使用。

同时保持精简。去除噪音:无果的失败尝试、冗长的工具输出、反复探索。留下的应该是纯信号——保留完整理解、零歧义的精华细节。

压缩块占位符
当选定范围包含之前压缩的块时,引用时使用以下精确占位符格式:

- \`(bN)\`

Compressed block sections in context are clearly marked with a header:
上下文中的压缩块部分用标题明确标记:

- \`[Compressed conversation section]\`
- \`[已压缩的对话部分]\`

Compressed block IDs always use the \`bN\` form (never \`mNNNN\`) and are represented in the same XML metadata tag format.
压缩块 ID 始终使用 \`bN\` 形式(不是 \`mNNNN\`),并以相同的 XML 元数据标签格式表示。

Rules:
规则:

- Include every required block placeholder exactly once.
- Do not invent placeholders for blocks outside the selected range.
- Treat \`(bN)\` placeholders as RESERVED TOKENS. Do not emit \`(bN)\` text anywhere except intentional placeholders.
- If you need to mention a block in prose, use plain text like \`compressed bN\` (not as a placeholder).
- Preflight check before finalizing: the set of \`(bN)\` placeholders in your summary must exactly match the required set, with no duplicates.
- 每个必需的块占位符恰好包含一次。
- 不要为选定范围之外的块发明占位符。
- \`(bN)\` 占位符视为保留标记。除了有意占位符外,不要在任何地方输出 \`(bN)\` 文本。
- 如果需要在正文中提及一个块,使用纯文本如 \`压缩块 bN\`(不作为占位符)。
- 最终确定前的预检:摘要中的 \`(bN)\` 占位符集合必须与必需集合完全匹配,无重复。

These placeholders are semantic references. They will be replaced with the full stored compressed block content when the tool processes your output.
这些占位符是语义引用。当工具处理你的输出时,它们将被替换为完整的存储压缩块内容。

FLOW PRESERVATION WITH PLACEHOLDERS
When you use compressed block placeholders, write the surrounding summary text so it still reads correctly AFTER placeholder expansion.
占位符的流畅性
使用压缩块占位符时,编写周围的摘要文本,使其在占位符展开后仍然正确可读。

- Treat each placeholder as a stand-in for a full conversation segment, not as a short label.
- Ensure transitions before and after each placeholder preserve chronology and causality.
- Do not write text that depends on the placeholder staying literal (for example, "as noted in \`(b2)\`").
- Your final meaning must be coherent once each placeholder is replaced with its full compressed block content.
- 将每个占位符视为完整对话段的替代,而不是短标签。
- 确保每个占位符前后的过渡保留时间顺序和因果关系。
- 不要编写依赖于占位符保持字面意义的文本(例如"如 \`(b2)\` 中所述")。
- 当每个占位符被替换为其完整压缩块内容后,最终含义必须连贯。

BOUNDARY IDS
You specify boundaries by ID using the injected IDs visible in the conversation:
边界 ID
使用对话中可见的注入 ID 按 ID 指定边界:

- \`mNNNN\` IDs identify raw messages
- \`bN\` IDs identify previously compressed blocks
- \`mNNNN\` ID 标识原始消息
- \`bN\` ID 标识之前压缩的块

Each message has an ID inside XML metadata tags like \`<dcp-message-id>...</dcp-message-id>\`.
The same ID tag appears in every tool output of the message it belongs to — each unique ID identifies one complete message.
Treat these tags as boundary metadata only, not as tool result content.
每条消息在 XML 元数据标签内有 ID,如 \`\`。
相同的 ID 标签出现在该消息所属的每个工具输出中——每个唯一 ID 标识一条完整消息。
将这些标签仅视为边界元数据,而非工具结果内容。

Rules:
规则:

- Pick \`startId\` and \`endId\` directly from injected IDs in context.
- IDs must exist in the current visible context.
- \`startId\` must appear before \`endId\`.
- Do not invent IDs. Use only IDs that are present in context.
- 直接从上下文中的注入 ID 选取 \`startId\` \`endId\`
- ID 必须存在于当前可见上下文中。
- \`startId\` 必须出现在 \`endId\` 之前。
- 不要发明 ID。只使用上下文中存在的 ID。

BATCHING
When multiple independent ranges are ready and their boundaries do not overlap, include all of them as separate entries in the \`content\` array of a single tool call. Each entry should have its own \`startId\`, \`endId\`, and \`summary\`.
批量处理
当多个独立范围已准备好且边界不重叠时,将它们全部作为单独条目包含在单次工具调用的 \`content\` 数组中。每个条目应有自己的 \`startId\`\`endId\` \`summary\`
`
20 changes: 11 additions & 9 deletions lib/prompts/extensions/nudge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,24 @@ export function buildCompressedBlockGuidance(state: SessionState): string {
.sort((a, b) => a - b)
.map((id) => `b${id}`)
const blockCount = refs.length
const blockList = blockCount > 0 ? refs.join(", ") : "none"
const blockList = blockCount > 0 ? refs.join(", ") : ""

return [
"Compressed block context:",
`- Active compressed blocks in this session: ${blockCount} (${blockList})`,
"- If your selected compression range includes any listed block, include each required placeholder exactly once in the summary using `(bN)`.",
"压缩块上下文:",
`- 此会话中的活跃压缩块:${blockCount} 个(${blockList}`,
"- 如果你选择的压缩范围包含任何列出的块,在摘要中使用 `(bN)` 恰好包含每个必需的占位符一次。",
].join("\n")
}

export function renderMessagePriorityGuidance(priorityLabel: string, refs: string[]): string {
const refList = refs.length > 0 ? refs.join(", ") : "none"
const refList = refs.length > 0 ? refs.join(", ") : "无"

const priorityLabelZh = priorityLabel === "high" ? "高" : priorityLabel === "medium" ? "中" : "低"

return [
"Message priority context:",
"- Higher-priority older messages consume more context and should be compressed right away if it is safe to do so.",
`- ${priorityLabel}-priority message IDs before this point: ${refList}`,
"消息优先级上下文:",
"- 高优先级的旧消息消耗更多上下文,如果安全的话应立即压缩。",
`- 此点之前的${priorityLabelZh}优先级消息 ID:${refList}`,
].join("\n")
}

Expand All @@ -30,7 +32,7 @@ export function appendGuidanceToDcpTag(nudgeText: string, guidance: string): str
return nudgeText
}

const closeTag = "</dcp-system-reminder>"
const closeTag = ""
const closeTagIndex = nudgeText.lastIndexOf(closeTag)

if (closeTagIndex === -1) {
Expand Down
26 changes: 5 additions & 21 deletions lib/prompts/extensions/system.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
export const MANUAL_MODE_SYSTEM_EXTENSION = `<dcp-system-reminder>
Manual mode is enabled. Do NOT use compress unless the user has explicitly triggered it through a manual marker.

Only use the compress tool after seeing \`<compress triggered manually>\` in the current user instruction context.

Issue exactly ONE compress tool per manual trigger. Do NOT launch multiple compress tools in parallel. Each trigger grants a single compression; after it completes, wait for the next trigger.

After completing a manually triggered context-management action, STOP IMMEDIATELY. Do NOT continue with any task execution. End your response right after the tool use completes and wait for the next user input.
</dcp-system-reminder>
export const MANUAL_MODE_SYSTEM_EXTENSION = `
手动模式已启用。压缩操作需要用户明确触发。
`

export const SUBAGENT_SYSTEM_EXTENSION = `<dcp-system-reminder>
You are operating in a subagent environment.

The initial subagent instruction is imperative and must be followed exactly.
It is the only user message intentionally not assigned a message ID, and therefore is not eligible for compression.
All subsequent messages in the session will have IDs.
</dcp-system-reminder>
export const SUBAGENT_SYSTEM_EXTENSION = `
子代理模式:压缩功能已禁用。
`

export function buildProtectedToolsExtension(protectedTools: string[]): string {
Expand All @@ -24,9 +12,5 @@ export function buildProtectedToolsExtension(protectedTools: string[]): string {
}

const toolList = protectedTools.map((t) => `\`${t}\``).join(", ")
return `<dcp-system-reminder>
The following tools are environment-managed: ${toolList}.
Their outputs are automatically preserved during compression.
Do not include their content in compress tool summaries — the environment retains it independently.
</dcp-system-reminder>`
return `以下工具的输出受保护,压缩时不要包含其完整内容:${toolList}`
}
Loading