Skip to content

Commit d267970

Browse files
authored
Fix empty tool_output in transcripts for subagent tools (#179)
* Enable keyword detection on first message using direct parts transformation Previously, first messages were skipped entirely to avoid interfering with title generation. Now, keywords detected on the first message are injected directly into the message parts instead of using the hook message injection system, allowing keywords like 'ultrawork' to activate on the first message of a session. This change: - Removes the early return that skipped first message keyword detection - Moves keyword context generation before the isFirstMessage check - For first messages: transforms message parts directly by prepending keyword context - For subsequent messages: maintains existing hook message injection behavior 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) * Preserve agent context in preemptive compaction's continue message When sending the 'Continue' message after compaction, now includes the original agent parameter from the stored message. Previously, the Continue message was sent without the agent parameter, causing OpenCode to use the default 'build' agent instead of preserving the original agent context (e.g., Sisyphus). Implementation: - Get messageDir using getMessageDir(sessionID) - Retrieve storedMessage using findNearestMessageWithFields - Pass agent: storedMessage?.agent to promptAsync body 🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) * Fix tool_result recording for call_omo_agent to include output in transcripts (#177) - Check if metadata is empty before using it - Wrap output.output in structured object when metadata is missing - Ensures plugin tools (call_omo_agent, background_task, task) that return strings are properly recorded in transcripts instead of empty {} 🤖 Generated with assistance of OhMyOpenCode
1 parent 17ccf6b commit d267970

File tree

3 files changed

+27
-8
lines changed

3 files changed

+27
-8
lines changed

src/hooks/claude-code-hooks/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,13 @@ export function createClaudeCodeHooksHook(ctx: PluginInput, config: PluginConfig
184184

185185
const cachedInput = getToolInput(input.sessionID, input.tool, input.callID) || {}
186186

187-
recordToolResult(input.sessionID, input.tool, cachedInput, (output.metadata as Record<string, unknown>) || {})
187+
// Use metadata if available and non-empty, otherwise wrap output.output in a structured object
188+
// This ensures plugin tools (call_omo_agent, background_task, task) that return strings
189+
// get their results properly recorded in transcripts instead of empty {}
190+
const metadata = output.metadata as Record<string, unknown> | undefined
191+
const hasMetadata = metadata && typeof metadata === "object" && Object.keys(metadata).length > 0
192+
const toolOutput = hasMetadata ? metadata : { output: output.output }
193+
recordToolResult(input.sessionID, input.tool, cachedInput, toolOutput)
188194

189195
if (!isHookDisabled(config, "PostToolUse")) {
190196
const postClient: PostToolUseClient = {

src/hooks/keyword-detector/index.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,26 @@ export function createKeywordDetectorHook() {
2525
const isFirstMessage = !sessionFirstMessageProcessed.has(input.sessionID)
2626
sessionFirstMessageProcessed.add(input.sessionID)
2727

28-
if (isFirstMessage) {
29-
log("Skipping keyword detection on first message for title generation", { sessionID: input.sessionID })
30-
return
31-
}
32-
3328
const promptText = extractPromptText(output.parts)
3429
const messages = detectKeywords(promptText)
3530

3631
if (messages.length === 0) {
3732
return
3833
}
3934

35+
const context = messages.join("\n")
36+
37+
// First message: transform parts directly (for title generation compatibility)
38+
if (isFirstMessage) {
39+
log(`Keywords detected on first message, transforming parts directly`, { sessionID: input.sessionID, keywordCount: messages.length })
40+
const idx = output.parts.findIndex((p) => p.type === "text" && p.text)
41+
if (idx >= 0) {
42+
output.parts[idx].text = `${context}\n\n---\n\n${output.parts[idx].text ?? ""}`
43+
}
44+
return
45+
}
46+
47+
// Subsequent messages: inject as separate message
4048
log(`Keywords detected: ${messages.length}`, { sessionID: input.sessionID })
4149

4250
const message = output.message as {
@@ -46,7 +54,6 @@ export function createKeywordDetectorHook() {
4654
tools?: Record<string, boolean>
4755
}
4856

49-
const context = messages.join("\n")
5057
log(`[keyword-detector] Injecting context for ${messages.length} keywords`, { sessionID: input.sessionID, contextLength: context.length })
5158
const success = injectHookMessage(input.sessionID, context, {
5259
agent: message.agent,

src/hooks/preemptive-compaction/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,15 @@ export function createPreemptiveCompactionHook(
184184

185185
setTimeout(async () => {
186186
try {
187+
const messageDir = getMessageDir(sessionID)
188+
const storedMessage = messageDir ? findNearestMessageWithFields(messageDir) : null
189+
187190
await ctx.client.session.promptAsync({
188191
path: { id: sessionID },
189-
body: { parts: [{ type: "text", text: "Continue" }] },
192+
body: {
193+
agent: storedMessage?.agent,
194+
parts: [{ type: "text", text: "Continue" }],
195+
},
190196
query: { directory: ctx.directory },
191197
})
192198
} catch {}

0 commit comments

Comments
 (0)