Skip to content

Commit 305e7de

Browse files
logger
1 parent 146696a commit 305e7de

File tree

6 files changed

+609
-163
lines changed

6 files changed

+609
-163
lines changed

lib/compress-logger.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as fs from "fs"
2+
import * as path from "path"
3+
4+
// =============================================================================
5+
// Dedicated compress diagnostics logger for DCP
6+
// Modeled after pocket-universe logger: buffered async file writes, always-on
7+
// Logs to .logs/dcp-compress.log relative to cwd
8+
// =============================================================================
9+
10+
const LOG_DIR = path.join(process.cwd(), ".logs")
11+
const LOG_FILE = path.join(LOG_DIR, "dcp-compress.log")
12+
const WRITE_INTERVAL_MS = 100
13+
const MAX_DATA_CHARS = 20000
14+
15+
let buffer: string[] = []
16+
let scheduled = false
17+
let initialized = false
18+
19+
function init(): boolean {
20+
if (initialized) return true
21+
try {
22+
if (!fs.existsSync(LOG_DIR)) {
23+
fs.mkdirSync(LOG_DIR, { recursive: true })
24+
}
25+
fs.writeFileSync(LOG_FILE, "")
26+
initialized = true
27+
return true
28+
} catch {
29+
return false
30+
}
31+
}
32+
33+
async function flush(): Promise<void> {
34+
if (buffer.length === 0) {
35+
scheduled = false
36+
return
37+
}
38+
const chunk = buffer.join("")
39+
buffer = []
40+
scheduled = false
41+
try {
42+
await fs.promises.appendFile(LOG_FILE, chunk)
43+
} catch {}
44+
}
45+
46+
function schedule(): void {
47+
if (!scheduled) {
48+
scheduled = true
49+
setTimeout(flush, WRITE_INTERVAL_MS)
50+
}
51+
}
52+
53+
type Level = "DEBUG" | "INFO" | "WARN" | "ERROR"
54+
55+
function shouldWrite(_level: Level): boolean {
56+
return true
57+
}
58+
59+
function stringifyData(data: unknown): string {
60+
try {
61+
const text = JSON.stringify(data)
62+
if (text.length <= MAX_DATA_CHARS) return text
63+
return `${text.substring(0, MAX_DATA_CHARS)}... [truncated ${text.length - MAX_DATA_CHARS} chars]`
64+
} catch {
65+
return `"[unserializable data]"`
66+
}
67+
}
68+
69+
function write(level: Level, category: string, message: string, data?: unknown): void {
70+
if (!init()) return
71+
if (!shouldWrite(level)) return
72+
const ts = new Date().toISOString()
73+
const dataStr = data !== undefined ? ` | ${stringifyData(data)}` : ""
74+
buffer.push(`[${ts}] [${level}] [${category}] ${message}${dataStr}\n`)
75+
schedule()
76+
}
77+
78+
export const clog = {
79+
debug: (category: string, message: string, data?: unknown) =>
80+
write("DEBUG", category, message, data),
81+
info: (category: string, message: string, data?: unknown) =>
82+
write("INFO", category, message, data),
83+
warn: (category: string, message: string, data?: unknown) =>
84+
write("WARN", category, message, data),
85+
error: (category: string, message: string, data?: unknown) =>
86+
write("ERROR", category, message, data),
87+
flush,
88+
}
89+
90+
export const C = {
91+
COMPRESS: "COMPRESS",
92+
BOUNDARY: "BOUNDARY",
93+
PRUNE: "PRUNE",
94+
INJECT: "INJECT",
95+
STATE: "STATE",
96+
HOOK: "HOOK",
97+
} as const

lib/hooks.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { handleSweepCommand } from "./commands/sweep"
1414
import { handleManualToggleCommand, handleManualTriggerCommand } from "./commands/manual"
1515
import { ensureSessionInitialized } from "./state/state"
1616
import { getCurrentParams } from "./strategies/utils"
17+
import { clog, C } from "./compress-logger"
1718

1819
const INTERNAL_AGENT_SIGNATURES = [
1920
"You are a title generator",
@@ -101,9 +102,15 @@ export function createChatMessageTransformHandler(
101102
config: PluginConfig,
102103
) {
103104
return async (input: {}, output: { messages: WithParts[] }) => {
105+
clog.debug(
106+
C.HOOK,
107+
`messages.transform: ${output.messages.length} messages, session=${state.sessionId}`,
108+
)
109+
104110
await checkSession(client, state, logger, output.messages, config.manualMode.enabled)
105111

106112
if (state.isSubAgent) {
113+
clog.debug(C.HOOK, "Skipping DCP transforms for sub-agent session")
107114
return
108115
}
109116

@@ -114,8 +121,16 @@ export function createChatMessageTransformHandler(
114121
supersedeWrites(state, logger, config, output.messages)
115122
purgeErrors(state, logger, config, output.messages)
116123

124+
clog.debug(
125+
C.HOOK,
126+
`Before prune: ${output.messages.length} messages, prune.tools=${state.prune.tools.size}, prune.messages=${state.prune.messages.size}, summaries=${state.compressSummaries.length}`,
127+
)
128+
117129
prune(state, logger, config, output.messages)
118-
insertCompressToolContext(state, config, logger, output.messages)
130+
131+
clog.debug(C.HOOK, `After prune: ${output.messages.length} messages`)
132+
133+
insertCompressToolContext(state, config, output.messages)
119134

120135
applyPendingManualTriggerPrompt(state, output.messages, logger)
121136
annotateContext(output.messages)

lib/messages/inject.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { SessionState, WithParts } from "../state"
2-
import type { Logger } from "../logger"
32
import type { PluginConfig } from "../config"
43
import type { UserMessage } from "@opencode-ai/sdk/v2"
54
import { renderNudge } from "../prompts"
@@ -12,6 +11,7 @@ import {
1211
import { getFilePathsFromParameters, isProtected } from "../protected-file-patterns"
1312
import { getLastUserMessage, isMessageCompacted } from "../shared-utils"
1413
import { getCurrentTokenUsage } from "../strategies/utils"
14+
import { clog, C } from "../compress-logger"
1515

1616
function parsePercentageString(value: string, total: number): number | undefined {
1717
if (!value.endsWith("%")) return undefined
@@ -149,14 +149,15 @@ const buildContextPressureTools = (state: SessionState, config: PluginConfig): s
149149
export const insertCompressToolContext = (
150150
state: SessionState,
151151
config: PluginConfig,
152-
logger: Logger,
153152
messages: WithParts[],
154153
): void => {
155154
if (state.manualMode || state.pendingManualTrigger) {
155+
clog.debug(C.INJECT, "Skipping compress context injection (manual mode or pending trigger)")
156156
return
157157
}
158158

159159
if (config.tools.compress.permission === "deny") {
160+
clog.debug(C.INJECT, "Skipping compress context injection (compress denied)")
160161
return
161162
}
162163

@@ -170,38 +171,48 @@ export const insertCompressToolContext = (
170171
: undefined
171172

172173
if (state.lastToolPrune) {
173-
logger.debug("Last context operation was compress - injecting cooldown")
174+
clog.info(C.INJECT, "Injecting cooldown (lastToolPrune=true)")
174175
contentParts.push(wrapCooldownMessage())
175176
} else {
176177
if (config.tools.settings.contextPressureEnabled) {
177178
const contextPressureTools = buildContextPressureTools(state, config)
178179
if (contextPressureTools) {
179180
contentParts.push(contextPressureTools)
181+
clog.debug(C.INJECT, "Added context pressure tools list")
180182
}
181183
}
182184

183185
if (config.tools.settings.compressContextEnabled) {
184186
contentParts.push(buildCompressContext(state, messages))
187+
clog.debug(C.INJECT, "Added compress context block")
185188
}
186189

187190
if (shouldInjectLimitNudge(config, state, messages, providerId, modelId)) {
188-
logger.info("Injecting context-limit nudge")
191+
clog.info(C.INJECT, "Injecting context-limit nudge", {
192+
nudgeCounter: state.nudgeCounter,
193+
currentTokens: getCurrentTokenUsage(messages),
194+
})
189195
contentParts.push(renderNudge("context-limit"))
190196
} else if (
191197
config.tools.settings.nudgeEnabled &&
192198
state.nudgeCounter >= config.tools.settings.nudgeFrequency
193199
) {
194-
logger.info("Injecting frequency nudge")
200+
clog.info(C.INJECT, "Injecting frequency nudge", {
201+
nudgeCounter: state.nudgeCounter,
202+
nudgeFrequency: config.tools.settings.nudgeFrequency,
203+
})
195204
contentParts.push(renderNudge("frequency"))
196205
}
197206
}
198207

199208
if (contentParts.length === 0) {
209+
clog.debug(C.INJECT, "No content parts to inject")
200210
return
201211
}
202212

203213
const combinedContent = contentParts.join("\n")
204214
if (!lastUserMessage) {
215+
clog.warn(C.INJECT, "No last user message found, cannot inject")
205216
return
206217
}
207218

@@ -210,16 +221,25 @@ export const insertCompressToolContext = (
210221
(message) => !(message.info.role === "user" && isIgnoredUserMessage(message)),
211222
)
212223
if (!lastNonIgnoredMessage) {
224+
clog.warn(C.INJECT, "No non-ignored message found, cannot inject")
213225
return
214226
}
215227

216228
if (lastNonIgnoredMessage.info.role === "user") {
217229
const textPart = createSyntheticTextPart(lastNonIgnoredMessage, combinedContent)
218230
lastNonIgnoredMessage.parts.push(textPart)
231+
clog.debug(C.INJECT, "Injected as synthetic text part on user message", {
232+
msgId: lastNonIgnoredMessage.info.id,
233+
contentLen: combinedContent.length,
234+
})
219235
return
220236
}
221237

222238
const modelID = userInfo.model?.modelID || ""
223239
const toolPart = createSyntheticToolPart(lastNonIgnoredMessage, combinedContent, modelID)
224240
lastNonIgnoredMessage.parts.push(toolPart)
241+
clog.debug(C.INJECT, "Injected as synthetic tool part on assistant message", {
242+
msgId: lastNonIgnoredMessage.info.id,
243+
contentLen: combinedContent.length,
244+
})
225245
}

lib/messages/prune.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { PluginConfig } from "../config"
44
import { isMessageCompacted, getLastUserMessage } from "../shared-utils"
55
import { createSyntheticUserMessage, COMPRESS_SUMMARY_PREFIX } from "./utils"
66
import type { UserMessage } from "@opencode-ai/sdk/v2"
7+
import { clog, C } from "../compress-logger"
78

89
const PRUNED_TOOL_OUTPUT_REPLACEMENT =
910
"[Output removed to save context - information superseded or no longer needed]"
@@ -165,10 +166,20 @@ const filterCompressedRanges = (
165166
messages: WithParts[],
166167
): void => {
167168
if (!state.prune.messages?.size) {
169+
clog.debug(C.PRUNE, "filterCompressedRanges: no pruned messages, skipping")
168170
return
169171
}
170172

173+
clog.info(C.PRUNE, "filterCompressedRanges entry", {
174+
totalMessages: messages.length,
175+
prunedMessagesCount: state.prune.messages.size,
176+
compressSummariesCount: state.compressSummaries.length,
177+
summaryAnchors: state.compressSummaries.map((s) => s.anchorMessageId),
178+
})
179+
171180
const result: WithParts[] = []
181+
let injected = 0
182+
let pruned = 0
172183

173184
for (const msg of messages) {
174185
const msgId = msg.info.id
@@ -186,27 +197,42 @@ const filterCompressedRanges = (
186197
result.push(
187198
createSyntheticUserMessage(userMessage, summaryContent, userInfo.variant),
188199
)
200+
injected++
189201

190-
logger.info("Injected compress summary", {
202+
clog.info(C.PRUNE, `Injected compress summary at anchor`, {
191203
anchorMessageId: msgId,
192204
summaryLength: summary.summary.length,
205+
summaryPreview: summary.summary.substring(0, 80),
206+
baseUserMsgId: userMessage.info.id,
193207
})
194208
} else {
195-
logger.warn("No user message found for compress summary", {
209+
clog.error(C.PRUNE, `No user message found for compress summary injection`, {
196210
anchorMessageId: msgId,
211+
msgIndex,
212+
messageRoles: messages
213+
.slice(0, msgIndex + 1)
214+
.map((m, i) => `${i}:${m.info.role}`),
197215
})
198216
}
199217
}
200218

201219
// Skip messages that are in the prune list
202220
if (state.prune.messages.has(msgId)) {
221+
pruned++
203222
continue
204223
}
205224

206225
// Normal message, include it
207226
result.push(msg)
208227
}
209228

229+
clog.info(C.PRUNE, "filterCompressedRanges complete", {
230+
inputMessages: messages.length,
231+
outputMessages: result.length,
232+
summariesInjected: injected,
233+
messagesPruned: pruned,
234+
})
235+
210236
// Replace messages array contents
211237
messages.length = 0
212238
messages.push(...result)

0 commit comments

Comments
 (0)