From e42608281bf46f9cd9d36bc553a7eef3c4579213 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 07:19:53 +0000 Subject: [PATCH 1/6] fix(kap-server): only fold user-origin steers by content in the cold transcript --- .changeset/steered-contents-origin-guard.md | 5 +++ .../services/transcript/transcriptService.ts | 2 + .../test/services/transcript.test.ts | 44 +++++++++++++++++++ packages/transcript/src/history/groupTurns.ts | 5 ++- packages/transcript/test/layers.test.ts | 25 +++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 .changeset/steered-contents-origin-guard.md diff --git a/.changeset/steered-contents-origin-guard.md b/.changeset/steered-contents-origin-guard.md new file mode 100644 index 00000000000..e20412dfd95 --- /dev/null +++ b/.changeset/steered-contents-origin-guard.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fixed skill instructions injected by the Skill tool showing up as ordinary user messages in the rebuilt transcript. diff --git a/packages/kap-server/src/services/transcript/transcriptService.ts b/packages/kap-server/src/services/transcript/transcriptService.ts index 49db89cb046..ee972421f3a 100644 --- a/packages/kap-server/src/services/transcript/transcriptService.ts +++ b/packages/kap-server/src/services/transcript/transcriptService.ts @@ -492,6 +492,8 @@ export class TranscriptService { continue; } if (record.type === 'turn.steer') { + const steerOrigin = (record as { origin?: { kind?: unknown } }).origin; + if (steerOrigin !== undefined && steerOrigin.kind !== 'user') continue; const input = record['input']; if (Array.isArray(input)) { const key = JSON.stringify(input); diff --git a/packages/kap-server/test/services/transcript.test.ts b/packages/kap-server/test/services/transcript.test.ts index 78aba43ddf3..9fb9acc22e5 100644 --- a/packages/kap-server/test/services/transcript.test.ts +++ b/packages/kap-server/test/services/transcript.test.ts @@ -2589,6 +2589,50 @@ describe('AgentTranscriptProjector', () => { } }); + it('readColdSnapshot keeps skill-activation steers as skill markers instead of user frames', async () => { + const home = await mkdtemp(join(tmpdir(), 'transcript-cold-skillsteer-')); + try { + const wireDir = join(home, 'sessions', 'ws', 's1', 'agents', 'main'); + await mkdir(wireDir, { recursive: true }); + const skillOrigin = { + kind: 'skill_activation', + activationId: 'a1', + skillName: 'write-tui', + trigger: 'model-tool', + skillSource: 'project', + }; + const nestedOrigin = { ...skillOrigin, activationId: 'a2', skillName: 'design', trigger: 'nested-skill' }; + const skillText = 'Skill tool loaded instructions for this request. Follow them.'; + const nestedText = 'Nested skill instructions.'; + const records = [ + { type: 'context.append_message', message: { role: 'user', content: [{ type: 'text', text: 'active' }], toolCalls: [], origin: { kind: 'user' } }, time: 1000 }, + { type: 'context.append_message', message: { role: 'assistant', content: [{ type: 'text', text: 'working' }], toolCalls: [] }, time: 2000 }, + { type: 'turn.steer', input: [{ type: 'text', text: skillText }], origin: skillOrigin, time: 3000 }, + { type: 'context.append_message', message: { role: 'user', content: [{ type: 'text', text: skillText }], toolCalls: [], origin: skillOrigin }, time: 3001 }, + { type: 'turn.steer', input: [{ type: 'text', text: nestedText }], origin: nestedOrigin, time: 3002 }, + { type: 'context.append_message', message: { role: 'user', content: [{ type: 'text', text: nestedText }], toolCalls: [], origin: nestedOrigin }, time: 3003 }, + { type: 'context.append_message', message: { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, time: 4000 }, + ]; + await writeFile(join(wireDir, 'wire.jsonl'), `${records.map((r) => JSON.stringify(r)).join('\n')}\n`); + + const snapshot = await coldTranscriptService(home).readColdSnapshot('s1', 'main'); + const markers = snapshot!.items.filter((item) => item.kind === 'marker'); + expect(markers).toHaveLength(2); + expect(markers.every((item) => item.kind === 'marker' && item.marker === 'skill')).toBe(true); + const turns = snapshot!.items.filter((item) => item.kind === 'turn'); + expect(turns).toHaveLength(1); + const turn = turns[0]; + if (turn?.kind !== 'turn') throw new Error('expected turn'); + expect(turn.prompt).toBe('active'); + const userFrames = turn.steps + .flatMap((step) => step.frames) + .filter((frame) => frame.kind === 'text' && frame.role === 'user'); + expect(userFrames).toHaveLength(0); + } finally { + await rm(home, { recursive: true, force: true }); + } + }); + it('readColdSnapshot drops undone task-turn boundaries so a redelivered notification folds', async () => { const home = await mkdtemp(join(tmpdir(), 'transcript-cold-undoboundary-')); try { diff --git a/packages/transcript/src/history/groupTurns.ts b/packages/transcript/src/history/groupTurns.ts index 82693f8b864..d5ffb780582 100644 --- a/packages/transcript/src/history/groupTurns.ts +++ b/packages/transcript/src/history/groupTurns.ts @@ -218,7 +218,10 @@ export function groupMessagesIntoSnapshot( continue; } const contentKey = JSON.stringify(message.content ?? []); - const steeredRemaining = steeredContents.get(contentKey) ?? 0; + const steeredRemaining = + originKind === undefined || originKind === 'user' + ? (steeredContents.get(contentKey) ?? 0) + : 0; if (steeredRemaining > 0) { steeredContents.set(contentKey, steeredRemaining - 1); const bundled = bundledSkillActivations(message); diff --git a/packages/transcript/test/layers.test.ts b/packages/transcript/test/layers.test.ts index 95f80ba6943..109a64a7858 100644 --- a/packages/transcript/test/layers.test.ts +++ b/packages/transcript/test/layers.test.ts @@ -1099,6 +1099,31 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { expect(slashTurn.steps).toHaveLength(2); }); + it('keeps model-tool skill activations as markers even when their content matches a steer record', () => { + const skillContent = [{ type: 'text', text: 'skill body' }]; + const snapshot = groupMessagesIntoSnapshot( + [ + { role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'answer' }], toolCalls: [] }, + { + role: 'user', + content: skillContent, + toolCalls: [], + origin: { kind: 'skill_activation', trigger: 'model-tool', skillName: 'write-tui' } as { + kind: string; + }, + }, + { role: 'assistant', content: [{ type: 'text', text: 'used the skill' }], toolCalls: [] }, + ], + { steeredContents: new Map([[JSON.stringify(skillContent), 1]]) }, + ); + + expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'marker']); + const marker = snapshot.items[1]; + if (marker?.kind !== 'marker') throw new Error('expected marker'); + expect(marker.marker).toBe('skill'); + }); + it('starts a promptless turn for turn-opening system triggers (goal continuation)', () => { const snapshot = groupMessagesIntoSnapshot([ { role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } }, From d7631064b5b7dff2a0d6dc33dcda610c227935d2 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 07:57:51 +0000 Subject: [PATCH 2/6] fix(transcript): check marker-only origins before the steer content match --- .../services/transcript/transcriptService.ts | 2 -- packages/transcript/src/history/groupTurns.ts | 23 +++++++-------- packages/transcript/test/layers.test.ts | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/packages/kap-server/src/services/transcript/transcriptService.ts b/packages/kap-server/src/services/transcript/transcriptService.ts index ee972421f3a..49db89cb046 100644 --- a/packages/kap-server/src/services/transcript/transcriptService.ts +++ b/packages/kap-server/src/services/transcript/transcriptService.ts @@ -492,8 +492,6 @@ export class TranscriptService { continue; } if (record.type === 'turn.steer') { - const steerOrigin = (record as { origin?: { kind?: unknown } }).origin; - if (steerOrigin !== undefined && steerOrigin.kind !== 'user') continue; const input = record['input']; if (Array.isArray(input)) { const key = JSON.stringify(input); diff --git a/packages/transcript/src/history/groupTurns.ts b/packages/transcript/src/history/groupTurns.ts index d5ffb780582..624d7855ccd 100644 --- a/packages/transcript/src/history/groupTurns.ts +++ b/packages/transcript/src/history/groupTurns.ts @@ -217,11 +217,17 @@ export function groupMessagesIntoSnapshot( } continue; } + const markerKey = originKind !== undefined ? MARKER_USER_ORIGINS[originKind] : undefined; + if (markerKey !== undefined) { + const opening = isUserSlashPrompt(message) ? foldTurnOpeningInput(message) : undefined; + pushMarker(markerKey, { text: opening?.text ?? textOf(message), origin: message.origin }); + if (opening !== undefined) { + startTurn(mapOrigin(message), opening.text, opening.attachmentIds); + } + continue; + } const contentKey = JSON.stringify(message.content ?? []); - const steeredRemaining = - originKind === undefined || originKind === 'user' - ? (steeredContents.get(contentKey) ?? 0) - : 0; + const steeredRemaining = steeredContents.get(contentKey) ?? 0; if (steeredRemaining > 0) { steeredContents.set(contentKey, steeredRemaining - 1); const bundled = bundledSkillActivations(message); @@ -242,15 +248,6 @@ export function groupMessagesIntoSnapshot( }); continue; } - const markerKey = originKind !== undefined ? MARKER_USER_ORIGINS[originKind] : undefined; - if (markerKey !== undefined) { - const opening = isUserSlashPrompt(message) ? foldTurnOpeningInput(message) : undefined; - pushMarker(markerKey, { text: opening?.text ?? textOf(message), origin: message.origin }); - if (opening !== undefined) { - startTurn(mapOrigin(message), opening.text, opening.attachmentIds); - } - continue; - } if (isTaskOrigin) { const origin = message.origin as { taskId?: unknown } | undefined; const taskId = typeof origin?.taskId === 'string' ? origin.taskId : undefined; diff --git a/packages/transcript/test/layers.test.ts b/packages/transcript/test/layers.test.ts index 109a64a7858..ea5f203bcec 100644 --- a/packages/transcript/test/layers.test.ts +++ b/packages/transcript/test/layers.test.ts @@ -1124,6 +1124,34 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { expect(marker.marker).toBe('skill'); }); + it('still folds cron-origin steers into the running turn by content match', () => { + const cronContent = [{ type: 'text', text: 'cron tick' }]; + const snapshot = groupMessagesIntoSnapshot( + [ + { role: 'user', content: [{ type: 'text', text: 'active' }], toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'working' }], toolCalls: [] }, + { + role: 'user', + content: cronContent, + toolCalls: [], + origin: { kind: 'cron_job', jobId: 'job1' } as { kind: string }, + }, + { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, + ], + { steeredContents: new Map([[JSON.stringify(cronContent), 1]]) }, + ); + + expect(snapshot.items.map((item) => item.kind)).toEqual(['turn']); + const turn = snapshot.items[0]; + if (turn?.kind !== 'turn') throw new Error('expected turn'); + expect(turn.steps).toHaveLength(2); + expect(turn.steps[1]?.frames[0]).toMatchObject({ + kind: 'text', + role: 'user', + text: 'cron tick', + }); + }); + it('starts a promptless turn for turn-opening system triggers (goal continuation)', () => { const snapshot = groupMessagesIntoSnapshot([ { role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } }, From 902acc4260396569899010df45b0a3698c0382cc Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 08:18:12 +0000 Subject: [PATCH 3/6] fix(transcript): limit the steer bypass to marker-only skill triggers --- packages/transcript/src/history/groupTurns.ts | 16 ++++++---- packages/transcript/test/layers.test.ts | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/transcript/src/history/groupTurns.ts b/packages/transcript/src/history/groupTurns.ts index 624d7855ccd..658ee7b2c3c 100644 --- a/packages/transcript/src/history/groupTurns.ts +++ b/packages/transcript/src/history/groupTurns.ts @@ -218,12 +218,8 @@ export function groupMessagesIntoSnapshot( continue; } const markerKey = originKind !== undefined ? MARKER_USER_ORIGINS[originKind] : undefined; - if (markerKey !== undefined) { - const opening = isUserSlashPrompt(message) ? foldTurnOpeningInput(message) : undefined; - pushMarker(markerKey, { text: opening?.text ?? textOf(message), origin: message.origin }); - if (opening !== undefined) { - startTurn(mapOrigin(message), opening.text, opening.attachmentIds); - } + if (markerKey !== undefined && !isUserSlashPrompt(message)) { + pushMarker(markerKey, { text: textOf(message), origin: message.origin }); continue; } const contentKey = JSON.stringify(message.content ?? []); @@ -248,6 +244,14 @@ export function groupMessagesIntoSnapshot( }); continue; } + if (markerKey !== undefined) { + const opening = isUserSlashPrompt(message) ? foldTurnOpeningInput(message) : undefined; + pushMarker(markerKey, { text: opening?.text ?? textOf(message), origin: message.origin }); + if (opening !== undefined) { + startTurn(mapOrigin(message), opening.text, opening.attachmentIds); + } + continue; + } if (isTaskOrigin) { const origin = message.origin as { taskId?: unknown } | undefined; const taskId = typeof origin?.taskId === 'string' ? origin.taskId : undefined; diff --git a/packages/transcript/test/layers.test.ts b/packages/transcript/test/layers.test.ts index ea5f203bcec..f07873c8b37 100644 --- a/packages/transcript/test/layers.test.ts +++ b/packages/transcript/test/layers.test.ts @@ -1152,6 +1152,36 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { }); }); + it('still folds user-slash skill activations into the running turn by content match', () => { + const slashContent = [{ type: 'text', text: 'slash skill body' }]; + const snapshot = groupMessagesIntoSnapshot( + [ + { role: 'user', content: [{ type: 'text', text: 'active' }], toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'working' }], toolCalls: [] }, + { + role: 'user', + content: slashContent, + toolCalls: [], + origin: { kind: 'skill_activation', trigger: 'user-slash', skillName: 'gen-docs' } as { + kind: string; + }, + }, + { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, + ], + { steeredContents: new Map([[JSON.stringify(slashContent), 1]]) }, + ); + + expect(snapshot.items.map((item) => item.kind)).toEqual(['turn']); + const turn = snapshot.items[0]; + if (turn?.kind !== 'turn') throw new Error('expected turn'); + expect(turn.steps).toHaveLength(2); + expect(turn.steps[1]?.frames[0]).toMatchObject({ + kind: 'text', + role: 'user', + text: 'slash skill body', + }); + }); + it('starts a promptless turn for turn-opening system triggers (goal continuation)', () => { const snapshot = groupMessagesIntoSnapshot([ { role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } }, From 7078fc056109aca32bca4aaa0207dd0d4ed4e7d1 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 08:26:46 +0000 Subject: [PATCH 4/6] fix(transcript): consume the steer count for marker-only activations --- packages/transcript/src/history/groupTurns.ts | 3 +++ packages/transcript/test/layers.test.ts | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/transcript/src/history/groupTurns.ts b/packages/transcript/src/history/groupTurns.ts index 658ee7b2c3c..ad1b10d4379 100644 --- a/packages/transcript/src/history/groupTurns.ts +++ b/packages/transcript/src/history/groupTurns.ts @@ -220,6 +220,9 @@ export function groupMessagesIntoSnapshot( const markerKey = originKind !== undefined ? MARKER_USER_ORIGINS[originKind] : undefined; if (markerKey !== undefined && !isUserSlashPrompt(message)) { pushMarker(markerKey, { text: textOf(message), origin: message.origin }); + const markerContentKey = JSON.stringify(message.content ?? []); + const markerSteerRemaining = steeredContents.get(markerContentKey) ?? 0; + if (markerSteerRemaining > 0) steeredContents.set(markerContentKey, markerSteerRemaining - 1); continue; } const contentKey = JSON.stringify(message.content ?? []); diff --git a/packages/transcript/test/layers.test.ts b/packages/transcript/test/layers.test.ts index f07873c8b37..6f61eab16c9 100644 --- a/packages/transcript/test/layers.test.ts +++ b/packages/transcript/test/layers.test.ts @@ -1182,6 +1182,29 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { }); }); + it('consumes the steer count for marker-only activations so a later identical prompt opens its own turn', () => { + const shared = [{ type: 'text', text: 'same text' }]; + const snapshot = groupMessagesIntoSnapshot( + [ + { role: 'user', content: [{ type: 'text', text: 'active' }], toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'working' }], toolCalls: [] }, + { + role: 'user', + content: shared, + toolCalls: [], + origin: { kind: 'skill_activation', trigger: 'model-tool', skillName: 'x' } as { + kind: string; + }, + }, + { role: 'user', content: shared, toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, + ], + { steeredContents: new Map([[JSON.stringify(shared), 1]]) }, + ); + + expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'marker', 'turn']); + }); + it('starts a promptless turn for turn-opening system triggers (goal continuation)', () => { const snapshot = groupMessagesIntoSnapshot([ { role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } }, From c0ecfac7e8f1f68e50a5aa20f40d15b42047a725 Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 08:36:37 +0000 Subject: [PATCH 5/6] fix(transcript): pair steered contents with messages by origin kind --- .../services/transcript/transcriptService.ts | 9 +++- packages/transcript/src/history/groupTurns.ts | 17 ++++---- packages/transcript/test/layers.test.ts | 43 +++++++++++++++---- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/packages/kap-server/src/services/transcript/transcriptService.ts b/packages/kap-server/src/services/transcript/transcriptService.ts index 49db89cb046..6c2eea45d05 100644 --- a/packages/kap-server/src/services/transcript/transcriptService.ts +++ b/packages/kap-server/src/services/transcript/transcriptService.ts @@ -466,7 +466,7 @@ export class TranscriptService { } const messages = [...reduceContextTranscript(records).entries]; const taskOriginTurnTaskIds = new Set(); - const steeredContents = new Map(); + const steeredContents = new Map>(); const anchorStack: { taskIdsSnapshot: Set }[] = []; let anchorFloor = 0; let sawTurnPrompt = false; @@ -495,7 +495,12 @@ export class TranscriptService { const input = record['input']; if (Array.isArray(input)) { const key = JSON.stringify(input); - steeredContents.set(key, (steeredContents.get(key) ?? 0) + 1); + const kind = + ((record as { origin?: { kind?: unknown } }).origin?.kind as string | undefined) ?? + 'user'; + const byKind = steeredContents.get(key) ?? new Map(); + byKind.set(kind, (byKind.get(kind) ?? 0) + 1); + steeredContents.set(key, byKind); } continue; } diff --git a/packages/transcript/src/history/groupTurns.ts b/packages/transcript/src/history/groupTurns.ts index ad1b10d4379..6e41bb27976 100644 --- a/packages/transcript/src/history/groupTurns.ts +++ b/packages/transcript/src/history/groupTurns.ts @@ -67,12 +67,14 @@ export function groupMessagesIntoSnapshot( messages: readonly HistoryMessage[], options?: { readonly taskOriginTurnTaskIds?: ReadonlySet; - readonly steeredContents?: ReadonlyMap; + readonly steeredContents?: ReadonlyMap>; }, ): AgentTranscriptSnapshot { const items: TranscriptItem[] = []; const attachments: TranscriptAttachment[] = []; - const steeredContents = new Map(options?.steeredContents ?? []); + const steeredContents = new Map( + [...(options?.steeredContents ?? [])].map(([key, byKind]) => [key, new Map(byKind)]), + ); let turn: TurnDraft | undefined; let pendingNotificationFrames: { text: string; @@ -220,15 +222,14 @@ export function groupMessagesIntoSnapshot( const markerKey = originKind !== undefined ? MARKER_USER_ORIGINS[originKind] : undefined; if (markerKey !== undefined && !isUserSlashPrompt(message)) { pushMarker(markerKey, { text: textOf(message), origin: message.origin }); - const markerContentKey = JSON.stringify(message.content ?? []); - const markerSteerRemaining = steeredContents.get(markerContentKey) ?? 0; - if (markerSteerRemaining > 0) steeredContents.set(markerContentKey, markerSteerRemaining - 1); continue; } const contentKey = JSON.stringify(message.content ?? []); - const steeredRemaining = steeredContents.get(contentKey) ?? 0; - if (steeredRemaining > 0) { - steeredContents.set(contentKey, steeredRemaining - 1); + const steerKind = originKind ?? 'user'; + const steeredByKind = steeredContents.get(contentKey); + const steeredRemaining = steeredByKind?.get(steerKind) ?? 0; + if (steeredByKind !== undefined && steeredRemaining > 0) { + steeredByKind.set(steerKind, steeredRemaining - 1); const bundled = bundledSkillActivations(message); const parts = message.content ?? []; bundled.forEach((activation, index) => { diff --git a/packages/transcript/test/layers.test.ts b/packages/transcript/test/layers.test.ts index 6f61eab16c9..8fb5a501301 100644 --- a/packages/transcript/test/layers.test.ts +++ b/packages/transcript/test/layers.test.ts @@ -506,7 +506,7 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { { role: 'user', content: [{ type: 'text', text: 'steered in' }], toolCalls: [], origin: { kind: 'user' } }, { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, ], - { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), 1]]) }, + { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), new Map([['user', 1]])]]) }, ); expect(snapshot.items.map((i) => i.kind)).toEqual(['turn']); @@ -527,7 +527,7 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { { role: 'assistant', content: [{ type: 'text', text: 'working' }], toolCalls: [] }, { role: 'user', content: [{ type: 'text', text: 'steered in' }], toolCalls: [], origin: { kind: 'user' } }, ], - { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), 1]]) }, + { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), new Map([['user', 1]])]]) }, ); expect(snapshot.items.map((i) => i.kind)).toEqual(['turn']); @@ -550,7 +550,7 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { { role: 'user', content: [{ type: 'text', text: 'next question' }], toolCalls: [], origin: { kind: 'user' } }, { role: 'assistant', content: [{ type: 'text', text: 'answer' }], toolCalls: [] }, ], - { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), 1]]) }, + { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), new Map([['user', 1]])]]) }, ); const turns = snapshot.items.filter((i) => i.kind === 'turn'); @@ -574,7 +574,7 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { { role: 'assistant', content: [{ type: 'text', text: 'working' }], toolCalls: [] }, { role: 'user', content: [{ type: 'text', text: 'plain follow-up' }], toolCalls: [], origin: { kind: 'user' } }, ], - { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), 1]]) }, + { steeredContents: new Map([[JSON.stringify([{ type: 'text', text: 'steered in' }]), new Map([['user', 1]])]]) }, ); expect(snapshot.items.map((i) => i.kind)).toEqual(['turn', 'turn']); @@ -1115,7 +1115,7 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { }, { role: 'assistant', content: [{ type: 'text', text: 'used the skill' }], toolCalls: [] }, ], - { steeredContents: new Map([[JSON.stringify(skillContent), 1]]) }, + { steeredContents: new Map([[JSON.stringify(skillContent), new Map([['skill_activation', 1]])]]) }, ); expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'marker']); @@ -1138,7 +1138,7 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { }, { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, ], - { steeredContents: new Map([[JSON.stringify(cronContent), 1]]) }, + { steeredContents: new Map([[JSON.stringify(cronContent), new Map([['cron_job', 1]])]]) }, ); expect(snapshot.items.map((item) => item.kind)).toEqual(['turn']); @@ -1168,7 +1168,7 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { }, { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, ], - { steeredContents: new Map([[JSON.stringify(slashContent), 1]]) }, + { steeredContents: new Map([[JSON.stringify(slashContent), new Map([['skill_activation', 1]])]]) }, ); expect(snapshot.items.map((item) => item.kind)).toEqual(['turn']); @@ -1199,12 +1199,39 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { { role: 'user', content: shared, toolCalls: [], origin: { kind: 'user' } }, { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, ], - { steeredContents: new Map([[JSON.stringify(shared), 1]]) }, + { steeredContents: new Map([[JSON.stringify(shared), new Map([['skill_activation', 1]])]]) }, ); expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'marker', 'turn']); }); + it('does not consume a user steer count for a compaction summary with identical content', () => { + const shared = [{ type: 'text', text: 'same text' }]; + const snapshot = groupMessagesIntoSnapshot( + [ + { role: 'user', content: [{ type: 'text', text: 'active' }], toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'working' }], toolCalls: [] }, + { + role: 'user', + content: shared, + toolCalls: [], + origin: { kind: 'compaction_summary' } as { kind: string }, + }, + { role: 'user', content: shared, toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'noted' }], toolCalls: [] }, + ], + { steeredContents: new Map([[JSON.stringify(shared), new Map([['user', 1]])]]) }, + ); + + expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'marker']); + const turn = snapshot.items[0]; + if (turn?.kind !== 'turn') throw new Error('expected turn'); + const steered = turn.steps + .flatMap((step) => step.frames) + .filter((frame) => frame.kind === 'text' && frame.role === 'user'); + expect(steered).toHaveLength(1); + }); + it('starts a promptless turn for turn-opening system triggers (goal continuation)', () => { const snapshot = groupMessagesIntoSnapshot([ { role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } }, From 1a4295adecb63ab9d992b2155cf40f59401c27aa Mon Sep 17 00:00:00 2001 From: kimi-agent-bot Date: Fri, 28 Aug 2026 08:51:31 +0000 Subject: [PATCH 6/6] refactor(kap-server): read the steer origin kind without nested casts --- .../kap-server/src/services/transcript/transcriptService.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/kap-server/src/services/transcript/transcriptService.ts b/packages/kap-server/src/services/transcript/transcriptService.ts index 6c2eea45d05..776786cdcbb 100644 --- a/packages/kap-server/src/services/transcript/transcriptService.ts +++ b/packages/kap-server/src/services/transcript/transcriptService.ts @@ -495,9 +495,8 @@ export class TranscriptService { const input = record['input']; if (Array.isArray(input)) { const key = JSON.stringify(input); - const kind = - ((record as { origin?: { kind?: unknown } }).origin?.kind as string | undefined) ?? - 'user'; + const steerOrigin = (record as { origin?: { kind?: unknown } }).origin?.kind; + const kind = typeof steerOrigin === 'string' ? steerOrigin : 'user'; const byKind = steeredContents.get(key) ?? new Map(); byKind.set(kind, (byKind.get(kind) ?? 0) + 1); steeredContents.set(key, byKind);