@royalcat/opencode-dcp-rc 0.0.0-stage → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/LICENSE +619 -0
  2. package/README.md +348 -2
  3. package/dist/index.d.ts +7 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +8129 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/commands/compression-targets.d.ts +13 -0
  8. package/dist/lib/commands/compression-targets.d.ts.map +1 -0
  9. package/dist/lib/commands/context.d.ts +67 -0
  10. package/dist/lib/commands/context.d.ts.map +1 -0
  11. package/dist/lib/commands/decompress.d.ts +12 -0
  12. package/dist/lib/commands/decompress.d.ts.map +1 -0
  13. package/dist/lib/commands/help.d.ts +18 -0
  14. package/dist/lib/commands/help.d.ts.map +1 -0
  15. package/dist/lib/commands/index.d.ts +8 -0
  16. package/dist/lib/commands/index.d.ts.map +1 -0
  17. package/dist/lib/commands/manual.d.ts +23 -0
  18. package/dist/lib/commands/manual.d.ts.map +1 -0
  19. package/dist/lib/commands/recompress.d.ts +12 -0
  20. package/dist/lib/commands/recompress.d.ts.map +1 -0
  21. package/dist/lib/commands/stats.d.ts +27 -0
  22. package/dist/lib/commands/stats.d.ts.map +1 -0
  23. package/dist/lib/commands/sweep.d.ts +23 -0
  24. package/dist/lib/commands/sweep.d.ts.map +1 -0
  25. package/dist/lib/compress/index.d.ts +3 -0
  26. package/dist/lib/compress/index.d.ts.map +1 -0
  27. package/dist/lib/compress/pipeline.d.ts +29 -0
  28. package/dist/lib/compress/pipeline.d.ts.map +1 -0
  29. package/dist/lib/compress/protected-content.d.ts +6 -0
  30. package/dist/lib/compress/protected-content.d.ts.map +1 -0
  31. package/dist/lib/compress/rc.d.ts +15 -0
  32. package/dist/lib/compress/rc.d.ts.map +1 -0
  33. package/dist/lib/compress/search.d.ts +12 -0
  34. package/dist/lib/compress/search.d.ts.map +1 -0
  35. package/dist/lib/compress/state.d.ts +10 -0
  36. package/dist/lib/compress/state.d.ts.map +1 -0
  37. package/dist/lib/compress/summary.d.ts +68 -0
  38. package/dist/lib/compress/summary.d.ts.map +1 -0
  39. package/dist/lib/compress/timing.d.ts +18 -0
  40. package/dist/lib/compress/timing.d.ts.map +1 -0
  41. package/dist/lib/compress/types.d.ts +64 -0
  42. package/dist/lib/compress/types.d.ts.map +1 -0
  43. package/dist/lib/compress/usage.d.ts +45 -0
  44. package/dist/lib/compress/usage.d.ts.map +1 -0
  45. package/dist/lib/compress-permission.d.ts +4 -0
  46. package/dist/lib/compress-permission.d.ts.map +1 -0
  47. package/dist/lib/config.d.ts +83 -0
  48. package/dist/lib/config.d.ts.map +1 -0
  49. package/dist/lib/hooks.d.ts +22 -0
  50. package/dist/lib/hooks.d.ts.map +1 -0
  51. package/dist/lib/logger.d.ts +31 -0
  52. package/dist/lib/logger.d.ts.map +1 -0
  53. package/dist/lib/message-ids.d.ts +20 -0
  54. package/dist/lib/message-ids.d.ts.map +1 -0
  55. package/dist/lib/messages/index.d.ts +8 -0
  56. package/dist/lib/messages/index.d.ts.map +1 -0
  57. package/dist/lib/messages/inject/inject.d.ts +7 -0
  58. package/dist/lib/messages/inject/inject.d.ts.map +1 -0
  59. package/dist/lib/messages/inject/subagent-results.d.ts +4 -0
  60. package/dist/lib/messages/inject/subagent-results.d.ts.map +1 -0
  61. package/dist/lib/messages/inject/utils.d.ts +23 -0
  62. package/dist/lib/messages/inject/utils.d.ts.map +1 -0
  63. package/dist/lib/messages/prune.d.ts +4 -0
  64. package/dist/lib/messages/prune.d.ts.map +1 -0
  65. package/dist/lib/messages/query.d.ts +7 -0
  66. package/dist/lib/messages/query.d.ts.map +1 -0
  67. package/dist/lib/messages/reasoning-strip.d.ts +8 -0
  68. package/dist/lib/messages/reasoning-strip.d.ts.map +1 -0
  69. package/dist/lib/messages/shape.d.ts +5 -0
  70. package/dist/lib/messages/shape.d.ts.map +1 -0
  71. package/dist/lib/messages/sync.d.ts +4 -0
  72. package/dist/lib/messages/sync.d.ts.map +1 -0
  73. package/dist/lib/messages/utils.d.ts +27 -0
  74. package/dist/lib/messages/utils.d.ts.map +1 -0
  75. package/dist/lib/prompts/compress-rc.d.ts +4 -0
  76. package/dist/lib/prompts/compress-rc.d.ts.map +1 -0
  77. package/dist/lib/prompts/context-limit-nudge.d.ts +2 -0
  78. package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -0
  79. package/dist/lib/prompts/extensions/nudge.d.ts +4 -0
  80. package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -0
  81. package/dist/lib/prompts/extensions/system.d.ts +4 -0
  82. package/dist/lib/prompts/extensions/system.d.ts.map +1 -0
  83. package/dist/lib/prompts/extensions/tool.d.ts +3 -0
  84. package/dist/lib/prompts/extensions/tool.d.ts.map +1 -0
  85. package/dist/lib/prompts/index.d.ts +4 -0
  86. package/dist/lib/prompts/index.d.ts.map +1 -0
  87. package/dist/lib/prompts/iteration-nudge.d.ts +2 -0
  88. package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -0
  89. package/dist/lib/prompts/store.d.ts +26 -0
  90. package/dist/lib/prompts/store.d.ts.map +1 -0
  91. package/dist/lib/prompts/system.d.ts +4 -0
  92. package/dist/lib/prompts/system.d.ts.map +1 -0
  93. package/dist/lib/prompts/turn-nudge.d.ts +2 -0
  94. package/dist/lib/prompts/turn-nudge.d.ts.map +1 -0
  95. package/dist/lib/protected-patterns.d.ts +6 -0
  96. package/dist/lib/protected-patterns.d.ts.map +1 -0
  97. package/dist/lib/state/index.d.ts +5 -0
  98. package/dist/lib/state/index.d.ts.map +1 -0
  99. package/dist/lib/state/persistence.d.ts +47 -0
  100. package/dist/lib/state/persistence.d.ts.map +1 -0
  101. package/dist/lib/state/state.d.ts +9 -0
  102. package/dist/lib/state/state.d.ts.map +1 -0
  103. package/dist/lib/state/tool-cache.d.ts +13 -0
  104. package/dist/lib/state/tool-cache.d.ts.map +1 -0
  105. package/dist/lib/state/types.d.ts +105 -0
  106. package/dist/lib/state/types.d.ts.map +1 -0
  107. package/dist/lib/state/utils.d.ts +22 -0
  108. package/dist/lib/state/utils.d.ts.map +1 -0
  109. package/dist/lib/strategies/deduplication.d.ts +10 -0
  110. package/dist/lib/strategies/deduplication.d.ts.map +1 -0
  111. package/dist/lib/strategies/index.d.ts +3 -0
  112. package/dist/lib/strategies/index.d.ts.map +1 -0
  113. package/dist/lib/strategies/purge-errors.d.ts +13 -0
  114. package/dist/lib/strategies/purge-errors.d.ts.map +1 -0
  115. package/dist/lib/subagents/subagent-results.d.ts +5 -0
  116. package/dist/lib/subagents/subagent-results.d.ts.map +1 -0
  117. package/dist/lib/token-utils.d.ts +19 -0
  118. package/dist/lib/token-utils.d.ts.map +1 -0
  119. package/dist/lib/tui/dialogs.d.ts +29 -0
  120. package/dist/lib/tui/dialogs.d.ts.map +1 -0
  121. package/dist/lib/tui/format.d.ts +4 -0
  122. package/dist/lib/tui/format.d.ts.map +1 -0
  123. package/dist/lib/tui/types.d.ts +18 -0
  124. package/dist/lib/tui/types.d.ts.map +1 -0
  125. package/dist/lib/tui/ui.d.ts +48 -0
  126. package/dist/lib/tui/ui.d.ts.map +1 -0
  127. package/dist/lib/ui/notification.d.ts +17 -0
  128. package/dist/lib/ui/notification.d.ts.map +1 -0
  129. package/dist/lib/ui/utils.d.ts +10 -0
  130. package/dist/lib/ui/utils.d.ts.map +1 -0
  131. package/dist/lib/v2/index.d.ts +5 -0
  132. package/dist/lib/v2/index.d.ts.map +1 -0
  133. package/dist/lib/v2/messages.d.ts +13 -0
  134. package/dist/lib/v2/messages.d.ts.map +1 -0
  135. package/dist/lib/v2/rpc.d.ts +75 -0
  136. package/dist/lib/v2/rpc.d.ts.map +1 -0
  137. package/dist/lib/v2/theme.d.ts +55 -0
  138. package/dist/lib/v2/theme.d.ts.map +1 -0
  139. package/dist/lib/v2/tui.d.ts +4 -0
  140. package/dist/lib/v2/tui.d.ts.map +1 -0
  141. package/dist/lib/v2/usage.d.ts +44 -0
  142. package/dist/lib/v2/usage.d.ts.map +1 -0
  143. package/dist/tui.d.ts +7 -0
  144. package/dist/tui.d.ts.map +1 -0
  145. package/lib/commands/compression-targets.ts +54 -0
  146. package/lib/commands/context.ts +305 -0
  147. package/lib/commands/decompress.ts +273 -0
  148. package/lib/commands/help.ts +76 -0
  149. package/lib/commands/index.ts +11 -0
  150. package/lib/commands/manual.ts +126 -0
  151. package/lib/commands/recompress.ts +222 -0
  152. package/lib/commands/stats.ts +180 -0
  153. package/lib/commands/sweep.ts +266 -0
  154. package/lib/compress/index.ts +2 -0
  155. package/lib/compress/pipeline.ts +116 -0
  156. package/lib/compress/protected-content.ts +163 -0
  157. package/lib/compress/rc.ts +494 -0
  158. package/lib/compress/search.ts +277 -0
  159. package/lib/compress/state.ts +272 -0
  160. package/lib/compress/summary.ts +320 -0
  161. package/lib/compress/timing.ts +77 -0
  162. package/lib/compress/types.ts +70 -0
  163. package/lib/compress/usage.ts +168 -0
  164. package/lib/compress-permission.ts +9 -0
  165. package/lib/config.ts +995 -0
  166. package/lib/hooks.ts +205 -0
  167. package/lib/logger.ts +226 -0
  168. package/lib/message-ids.ts +181 -0
  169. package/lib/messages/index.ts +7 -0
  170. package/lib/messages/inject/inject.ts +215 -0
  171. package/lib/messages/inject/subagent-results.ts +84 -0
  172. package/lib/messages/inject/utils.ts +304 -0
  173. package/lib/messages/prune.ts +238 -0
  174. package/lib/messages/query.ts +71 -0
  175. package/lib/messages/reasoning-strip.ts +40 -0
  176. package/lib/messages/shape.ts +50 -0
  177. package/lib/messages/sync.ts +124 -0
  178. package/lib/messages/utils.ts +190 -0
  179. package/lib/prompts/compress-rc.ts +25 -0
  180. package/lib/prompts/context-limit-nudge.ts +18 -0
  181. package/lib/prompts/extensions/nudge.ts +34 -0
  182. package/lib/prompts/extensions/system.ts +32 -0
  183. package/lib/prompts/extensions/tool.ts +21 -0
  184. package/lib/prompts/index.ts +29 -0
  185. package/lib/prompts/iteration-nudge.ts +6 -0
  186. package/lib/prompts/store.ts +452 -0
  187. package/lib/prompts/system.ts +41 -0
  188. package/lib/prompts/turn-nudge.ts +10 -0
  189. package/lib/protected-patterns.ts +159 -0
  190. package/lib/state/index.ts +4 -0
  191. package/lib/state/persistence.ts +321 -0
  192. package/lib/state/state.ts +214 -0
  193. package/lib/state/tool-cache.ts +99 -0
  194. package/lib/state/types.ts +117 -0
  195. package/lib/state/utils.ts +345 -0
  196. package/lib/strategies/deduplication.ts +125 -0
  197. package/lib/strategies/index.ts +2 -0
  198. package/lib/strategies/purge-errors.ts +86 -0
  199. package/lib/subagents/subagent-results.ts +75 -0
  200. package/lib/token-utils.ts +164 -0
  201. package/lib/tui/dialogs.tsx +261 -0
  202. package/lib/tui/format.ts +25 -0
  203. package/lib/tui/types.ts +25 -0
  204. package/lib/tui/ui.tsx +245 -0
  205. package/lib/ui/notification.ts +347 -0
  206. package/lib/ui/utils.ts +304 -0
  207. package/lib/v2/index.ts +448 -0
  208. package/lib/v2/messages.ts +252 -0
  209. package/lib/v2/rpc.ts +60 -0
  210. package/lib/v2/theme.ts +67 -0
  211. package/lib/v2/tui.tsx +115 -0
  212. package/lib/v2/usage.ts +423 -0
  213. package/package.json +91 -5
  214. package/server.js +1 -0
  215. package/tui.tsx +7 -0
@@ -0,0 +1,190 @@
1
+ import { createHash } from "node:crypto"
2
+ import type { SessionState, WithParts } from "../state"
3
+ import { isMessageCompacted } from "../state/utils"
4
+ import type { UserMessage } from "@opencode-ai/sdk/v2"
5
+ import type { IdFormat } from "../message-ids"
6
+
7
+ const SUMMARY_ID_HASH_LENGTH = 16
8
+ const DCP_PAIRED_TAG_REGEX = /<dcp[^>]*>[\s\S]*?<\/dcp[^>]*>/gi
9
+ const DCP_UNPAIRED_TAG_REGEX = /<\/?dcp[^>]*>/gi
10
+ const INJECTED_MESSAGE_ID_SUFFIX_REGEX = /(?<=\n)<dcp-message-id[^>]*>m\d+<\/dcp-message-id>\s*$/
11
+ const HALLUCINATED_PARAMETER_SUFFIX_REGEX = /(?<=\n)m\d+<\/parameter>\s*$/
12
+ const COMPACT_TAG_REGEX = /@(?:[1-9]\d*|b[1-9]\d*|blocked)@(?:[ \t]+\[(?:low|medium|high)\])?/gi
13
+
14
+ const generateStableId = (prefix: string, seed: string): string => {
15
+ const hash = createHash("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH)
16
+ return `${prefix}_${hash}`
17
+ }
18
+
19
+ export const createSyntheticUserMessage = (
20
+ baseMessage: WithParts,
21
+ content: string,
22
+ stableSeed?: string,
23
+ ): WithParts => {
24
+ const userInfo = baseMessage.info as UserMessage
25
+ const now = Date.now()
26
+ const deterministicSeed = stableSeed?.trim() || userInfo.id
27
+ const messageId = generateStableId("msg_dcp_summary", deterministicSeed)
28
+ const partId = generateStableId("prt_dcp_summary", deterministicSeed)
29
+
30
+ return {
31
+ info: {
32
+ id: messageId,
33
+ sessionID: userInfo.sessionID,
34
+ role: "user" as const,
35
+ agent: userInfo.agent,
36
+ model: userInfo.model,
37
+ time: { created: now },
38
+ },
39
+ parts: [
40
+ {
41
+ id: partId,
42
+ sessionID: userInfo.sessionID,
43
+ messageID: messageId,
44
+ type: "text" as const,
45
+ text: content,
46
+ },
47
+ ],
48
+ }
49
+ }
50
+
51
+ export const createSyntheticTextPart = (
52
+ baseMessage: WithParts,
53
+ content: string,
54
+ stableSeed?: string,
55
+ ) => {
56
+ const userInfo = baseMessage.info as UserMessage
57
+ const deterministicSeed = stableSeed?.trim() || userInfo.id
58
+ const partId = generateStableId("prt_dcp_text", deterministicSeed)
59
+
60
+ return {
61
+ id: partId,
62
+ sessionID: userInfo.sessionID,
63
+ messageID: userInfo.id,
64
+ type: "text" as const,
65
+ text: content,
66
+ }
67
+ }
68
+
69
+ type MessagePart = WithParts["parts"][number]
70
+ type ToolPart = Extract<MessagePart, { type: "tool" }>
71
+ type TextPart = Extract<MessagePart, { type: "text" }>
72
+
73
+ export const appendToLastTextPart = (message: WithParts, injection: string): boolean => {
74
+ const textPart = findLastTextPart(message)
75
+ if (!textPart) {
76
+ return false
77
+ }
78
+
79
+ return appendToTextPart(textPart, injection)
80
+ }
81
+
82
+ const findLastTextPart = (message: WithParts): TextPart | null => {
83
+ for (let i = message.parts.length - 1; i >= 0; i--) {
84
+ const part = message.parts[i]
85
+ if (part.type === "text") {
86
+ return part
87
+ }
88
+ }
89
+
90
+ return null
91
+ }
92
+
93
+ export const appendToTextPart = (part: TextPart, injection: string): boolean => {
94
+ if (typeof part.text !== "string") {
95
+ return false
96
+ }
97
+
98
+ const normalizedInjection = injection.replace(/^\n+/, "")
99
+ if (!normalizedInjection.trim()) {
100
+ return false
101
+ }
102
+ if (part.text.includes(normalizedInjection)) {
103
+ return true
104
+ }
105
+
106
+ const baseText = part.text.replace(/\n*$/, "")
107
+ part.text = baseText.length > 0 ? `${baseText}\n\n${normalizedInjection}` : normalizedInjection
108
+ return true
109
+ }
110
+
111
+ export const appendToAllToolParts = (message: WithParts, tag: string): boolean => {
112
+ let injected = false
113
+ for (const part of message.parts) {
114
+ if (part.type === "tool") {
115
+ injected = appendToToolPart(part, tag) || injected
116
+ }
117
+ }
118
+ return injected
119
+ }
120
+
121
+ export const appendToToolPart = (part: ToolPart, tag: string): boolean => {
122
+ if (part.state?.status !== "completed" || typeof part.state.output !== "string") {
123
+ return false
124
+ }
125
+ if (part.state.output.includes(tag)) {
126
+ return true
127
+ }
128
+
129
+ part.state.output = `${part.state.output}${tag}`
130
+ return true
131
+ }
132
+
133
+ export const hasContent = (message: WithParts): boolean => {
134
+ return message.parts.some(
135
+ (part) =>
136
+ (part.type === "text" &&
137
+ typeof part.text === "string" &&
138
+ part.text.trim().length > 0) ||
139
+ (part.type === "tool" &&
140
+ part.state?.status === "completed" &&
141
+ typeof part.state.output === "string"),
142
+ )
143
+ }
144
+
145
+ export function buildToolIdList(state: SessionState, messages: WithParts[]): string[] {
146
+ const toolIds: string[] = []
147
+ for (const msg of messages) {
148
+ if (isMessageCompacted(state, msg)) {
149
+ continue
150
+ }
151
+ const parts = Array.isArray(msg.parts) ? msg.parts : []
152
+ if (parts.length > 0) {
153
+ for (const part of parts) {
154
+ if (part.type === "tool" && part.callID && part.tool) {
155
+ toolIds.push(part.callID)
156
+ }
157
+ }
158
+ }
159
+ }
160
+ state.toolIdList = toolIds
161
+ return toolIds
162
+ }
163
+
164
+ export const stripHallucinationsFromString = (text: string, format: IdFormat = "xml"): string => {
165
+ if (format === "compact") text = text.replace(COMPACT_TAG_REGEX, "")
166
+ const withoutKnownSuffixes = text
167
+ .replace(INJECTED_MESSAGE_ID_SUFFIX_REGEX, "")
168
+ .replace(HALLUCINATED_PARAMETER_SUFFIX_REGEX, "")
169
+ return withoutKnownSuffixes
170
+ .replace(DCP_PAIRED_TAG_REGEX, "")
171
+ .replace(DCP_UNPAIRED_TAG_REGEX, "")
172
+ }
173
+
174
+ export const stripHallucinations = (messages: WithParts[], format: IdFormat = "xml"): void => {
175
+ for (const message of messages) {
176
+ for (const part of message.parts) {
177
+ if (part.type === "text" && typeof part.text === "string") {
178
+ part.text = stripHallucinationsFromString(part.text, format)
179
+ }
180
+
181
+ if (
182
+ part.type === "tool" &&
183
+ part.state?.status === "completed" &&
184
+ typeof part.state.output === "string"
185
+ ) {
186
+ part.state.output = stripHallucinationsFromString(part.state.output, format)
187
+ }
188
+ }
189
+ }
190
+ }
@@ -0,0 +1,25 @@
1
+ import type { IdFormat } from "../message-ids"
2
+
3
+ export function rcPrompt(format: IdFormat = "xml"): string {
4
+ const single = format === "compact" ? "`@4@`" : "`m0004`"
5
+ const block = format === "compact" ? "`@b2@`" : "`b2`"
6
+ const range = format === "compact" ? "`@4@-@8@`" : "`m0004-m0008`"
7
+ return `Compress stale conversation messages into technical summaries.
8
+
9
+ Pass \`ids\`: an ordered list of injected message IDs, block IDs, or inclusive
10
+ ranges. Each list item becomes one summary, so preserve the summary boundaries
11
+ you want. For example, {"ids": [${range}, ${single}]} creates one summary for
12
+ the range and one for the single message.
13
+
14
+ Use only IDs visible in the conversation. A compressed block (${block}) can be
15
+ used as a boundary: including it merges its summary into the new one and retires
16
+ the old block. Prefer one broad, comprehensive selection of stale or resolved
17
+ messages over several small calls. Do not compress messages whose exact code,
18
+ errors, or file contents may still be needed in the immediate next steps; do not
19
+ compress work that is still active.
20
+
21
+ The summaries are generated automatically during this tool call and replace the
22
+ selected messages in your next request.`
23
+ }
24
+
25
+ export const COMPRESS_RC = rcPrompt()
@@ -0,0 +1,18 @@
1
+ export const CONTEXT_LIMIT_NUDGE = `<dcp-system-reminder>
2
+ CRITICAL WARNING: MAX CONTEXT LIMIT REACHED
3
+
4
+ You are at or beyond the configured max context threshold. This is an emergency context-recovery moment.
5
+
6
+ You MUST use the \`compress\` tool now. Do not continue normal exploration until compression is handled.
7
+
8
+ If you are in the middle of a critical atomic operation, finish that atomic step first, then compress immediately.
9
+
10
+ SELECTION PROCESS
11
+ Start from older, resolved history and capture as much stale context as safely possible in one pass.
12
+ Avoid the newest active working messages unless it is clearly closed.
13
+
14
+ SUMMARY REQUIREMENTS
15
+ Your summary MUST cover all essential details from the selected messages so work can continue.
16
+ If the compressed range includes user messages, preserve user intent exactly. Prefer direct quotes for short user messages to avoid semantic drift.
17
+ </dcp-system-reminder>
18
+ `
@@ -0,0 +1,34 @@
1
+ import type { SessionState } from "../../state"
2
+ import { formatBlockRef } from "../../message-ids"
3
+
4
+ export function buildCompressedBlockGuidance(state: SessionState): string {
5
+ const refs = Array.from(state.prune.messages.activeBlockIds)
6
+ .filter((id) => Number.isInteger(id) && id > 0)
7
+ .sort((a, b) => a - b)
8
+ .map((id) => formatBlockRef(id, state.idFormat))
9
+ const blockCount = refs.length
10
+ const blockList = blockCount > 0 ? refs.join(", ") : "none"
11
+
12
+ return [
13
+ "Compressed block context:",
14
+ `- Active compressed blocks in this session: ${blockCount} (${blockList})`,
15
+ "- Compressed blocks can be used as selection boundaries: include a block ID (or a range spanning it) to merge its summary into a new summary and retire the old block.",
16
+ ].join("\n")
17
+ }
18
+
19
+ export function appendGuidanceToDcpTag(nudgeText: string, guidance: string): string {
20
+ if (!guidance.trim()) {
21
+ return nudgeText
22
+ }
23
+
24
+ const closeTag = "</dcp-system-reminder>"
25
+ const closeTagIndex = nudgeText.lastIndexOf(closeTag)
26
+
27
+ if (closeTagIndex === -1) {
28
+ return nudgeText
29
+ }
30
+
31
+ const beforeClose = nudgeText.slice(0, closeTagIndex).trimEnd()
32
+ const afterClose = nudgeText.slice(closeTagIndex)
33
+ return `${beforeClose}\n\n${guidance}\n${afterClose}`
34
+ }
@@ -0,0 +1,32 @@
1
+ export const MANUAL_MODE_SYSTEM_EXTENSION = `<dcp-system-reminder>
2
+ Manual mode is enabled. Do NOT use compress unless the user has explicitly triggered it through a manual marker.
3
+
4
+ Only use the compress tool after seeing \`<compress triggered manually>\` in the current user instruction context.
5
+
6
+ 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.
7
+
8
+ 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.
9
+ </dcp-system-reminder>
10
+ `
11
+
12
+ export const SUBAGENT_SYSTEM_EXTENSION = `<dcp-system-reminder>
13
+ You are operating in a subagent environment.
14
+
15
+ The initial subagent instruction is imperative and must be followed exactly.
16
+ It is the only user message intentionally not assigned a message ID, and therefore is not eligible for compression.
17
+ All subsequent messages in the session will have IDs.
18
+ </dcp-system-reminder>
19
+ `
20
+
21
+ export function buildProtectedToolsExtension(protectedTools: string[]): string {
22
+ if (protectedTools.length === 0) {
23
+ return ""
24
+ }
25
+
26
+ const toolList = protectedTools.map((t) => `\`${t}\``).join(", ")
27
+ return `<dcp-system-reminder>
28
+ The following tools are environment-managed: ${toolList}.
29
+ Their outputs are automatically preserved during compression.
30
+ Do not include their content in compress tool summaries — the environment retains it independently.
31
+ </dcp-system-reminder>`
32
+ }
@@ -0,0 +1,21 @@
1
+ // These format schemas are kept separate from the editable compress prompts
2
+ // so they cannot be modified via custom prompt overrides. The schemas must
3
+ // match the tool's input validation and are not safe to change independently.
4
+
5
+ import type { IdFormat } from "../../message-ids"
6
+
7
+ export function rcFormat(format: IdFormat = "xml"): string {
8
+ const single = format === "compact" ? "@4@" : "m0004"
9
+ const block = format === "compact" ? "@b2@" : "b2"
10
+ const range = format === "compact" ? "@4@-@8@" : "m0004-m0008"
11
+ return `
12
+ THE FORMAT OF COMPRESS
13
+
14
+ \`\`\`
15
+ {
16
+ ids: string[] // Injected message IDs, block IDs, or inclusive ranges; each item becomes one summary
17
+ }
18
+ \`\`\`
19
+
20
+ Examples: \`{"ids": ["${range}", "${single}"]}\` creates one summary for the range and a separate summary for the single message. \`{"ids": ["${block}"]}\` merges an existing compressed block into a new summary.`
21
+ }
@@ -0,0 +1,29 @@
1
+ import type { RuntimePrompts } from "./store"
2
+ export type { PromptStore, RuntimePrompts } from "./store"
3
+
4
+ export function renderSystemPrompt(
5
+ prompts: RuntimePrompts,
6
+ protectedToolsExtension?: string,
7
+ manual?: boolean,
8
+ subagent?: boolean,
9
+ ): string {
10
+ const extensions: string[] = []
11
+
12
+ if (protectedToolsExtension) {
13
+ extensions.push(protectedToolsExtension.trim())
14
+ }
15
+
16
+ if (manual) {
17
+ extensions.push(prompts.manualExtension.trim())
18
+ }
19
+
20
+ if (subagent) {
21
+ extensions.push(prompts.subagentExtension.trim())
22
+ }
23
+
24
+ return [prompts.system.trim(), ...extensions]
25
+ .filter(Boolean)
26
+ .join("\n\n")
27
+ .replace(/\n([ \t]*\n)+/g, "\n\n")
28
+ .trim()
29
+ }
@@ -0,0 +1,6 @@
1
+ export const ITERATION_NUDGE = `<dcp-system-reminder>
2
+ You've been iterating for a while after the last user message.
3
+
4
+ If there is a closed portion that is unlikely to be referenced immediately (for example, finished research before implementation), use the compress tool on it now.
5
+ </dcp-system-reminder>
6
+ `