fix: add type guards and null checks for UUID handling in snip functionality

- Add UUID type import and type guard in force-snip command
- Add null/undefined checks in snipCompact, snipProjection, and sessionStorage
- Improve optional chaining in SnipTool for safer property access
- Prevent potential runtime errors from invalid UUID values

Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
This commit is contained in:
DoSun 2026-05-15 14:25:42 +08:00
parent de83394e8a
commit ecb0df50d3
5 changed files with 14 additions and 10 deletions

View File

@ -79,13 +79,11 @@ Guidelines:
},
async call(input: SnipInput) {
// Snip implementation is handled by the query engine's projection system.
// The tool call itself records the intent; the query engine intercepts
// snip tool results and adjusts its message projection accordingly.
const ids = input?.message_ids ?? []
return {
data: {
snipped_count: input.message_ids.length,
summary: input.reason ?? `Snipped ${input.message_ids.length} messages`,
snipped_count: ids.length,
summary: input?.reason ?? `Snipped ${ids.length} messages`,
},
}
},

View File

@ -1,3 +1,4 @@
import type { UUID } from 'crypto'
import { randomUUID } from 'crypto'
import type { Command, LocalCommandCall } from '../types/command.js'
import type { Message } from '../types/message.js'
@ -25,7 +26,7 @@ const call: LocalCommandCall = async (_args, context) => {
// Collect UUIDs of every message that will be snipped (everything currently
// in the conversation). The next call to `snipCompactIfNeeded` will honour
// the boundary and strip these from the model-facing view.
const removedUuids = messages.map(m => m.uuid)
const removedUuids = messages.map(m => m.uuid).filter((u): u is UUID => !!u)
const boundaryMessage: Message = {
type: 'system',

View File

@ -126,12 +126,15 @@ export function snipCompactIfNeeded(
}
// Filter out messages whose UUIDs are listed in removedUuids
const removedSet = new Set(removedUuids)
// Guard: filter out any falsy entries (e.g. messages that lacked a uuid)
const removedSet = new Set<string>(
removedUuids.filter((u): u is string => typeof u === 'string'),
)
const kept: Message[] = []
let tokensFreed = 0
for (const msg of messages) {
if (removedSet.has(msg.uuid)) {
if (msg.uuid && removedSet.has(msg.uuid)) {
tokensFreed += estimateMessageTokens(msg)
continue
}

View File

@ -46,7 +46,7 @@ export function projectSnippedView(messages: Message[]): Message[] {
| undefined
if (meta?.removedUuids) {
for (const uuid of meta.removedUuids) {
removedSet.add(uuid)
if (typeof uuid === 'string') removedSet.add(uuid)
}
}
}

View File

@ -1996,7 +1996,9 @@ function applySnipRemovals(messages: Map<UUID, TranscriptMessage>): void {
for (const entry of messages.values()) {
const removedUuids = (entry as WithSnipMeta).snipMetadata?.removedUuids
if (!removedUuids) continue
for (const uuid of removedUuids) toDelete.add(uuid)
for (const uuid of removedUuids) {
if (uuid) toDelete.add(uuid)
}
}
if (toDelete.size === 0) return