From 1d19789c7576ce5e43d53bb156bf2be67751b941 Mon Sep 17 00:00:00 2001 From: haelyra <49814733+haelyra@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:28:12 -0400 Subject: [PATCH] fix(hooks): preserve metadata-marked human prompts --- scripts/hooks/session-end.js | 5 ++++- tests/hooks/hooks.test.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/scripts/hooks/session-end.js b/scripts/hooks/session-end.js index 60ba74720..cb4ba7ac1 100644 --- a/scripts/hooks/session-end.js +++ b/scripts/hooks/session-end.js @@ -49,7 +49,10 @@ function extractSessionSummary(transcriptPath) { const cleaned = stripAnsi(text).trim(); // Skip harness noise: local command echoes, caveats, system reminders. const isNoise = /^<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|task-notification)/i.test(cleaned); - if (cleaned && !isToolResult && !isNoise && !entry.isMeta) { + // `isMeta` is also used for genuine channel- and plugin-originated + // human prompts. Exclude known structured noise above instead of + // discarding every metadata-marked user turn. + if (cleaned && !isToolResult && !isNoise) { userMessages.push(cleaned.slice(0, 200)); } } diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 49d6f1e23..7a5a4e7c3 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -2118,6 +2118,41 @@ async function runTests() { passed++; else failed++; + if ( + await asyncTest('keeps isMeta human prompts while filtering structured transcript noise', async () => { + const testDir = createTestDir(); + const transcriptPath = path.join(testDir, 'transcript.jsonl'); + const lines = [ + JSON.stringify({ type: 'user', isMeta: true, content: 'Prompt delivered by a channel plugin' }), + JSON.stringify({ type: 'user', isMeta: true, content: 'internal harness context' }), + JSON.stringify({ + type: 'user', + message: { role: 'user', content: [{ type: 'tool_result', content: 'tool output' }] }, + }), + ]; + fs.writeFileSync(transcriptPath, lines.join('\n')); + + const result = await runScript( + path.join(scriptsDir, 'session-end.js'), + JSON.stringify({ transcript_path: transcriptPath }), + { HOME: testDir, USERPROFILE: testDir } + ); + assert.strictEqual(result.code, 0); + + const sessionsDir = getCanonicalSessionsDir(testDir); + const sessionFiles = fs.readdirSync(sessionsDir).filter(file => file.endsWith('.tmp')); + assert.strictEqual(sessionFiles.length, 1, 'Should create one session file'); + const content = fs.readFileSync(path.join(sessionsDir, sessionFiles[0]), 'utf8'); + assert.ok(content.includes('Prompt delivered by a channel plugin')); + assert.ok(!content.includes('internal harness context')); + assert.ok(!content.includes('tool output')); + assert.ok(content.includes('Total user messages: 1')); + cleanupTestDir(testDir); + }) + ) + passed++; + else failed++; + if ( await asyncTest('extracts tool names and file paths from transcript', async () => { const testDir = createTestDir();