(args: CtxReduceArgs, toolContext)
| 28 | .describe("Tag IDs to drop entirely. Ranges: '3-5', '1,2,9'"), |
| 29 | }, |
| 30 | async execute(args: CtxReduceArgs, toolContext) { |
| 31 | const sessionId = toolContext.sessionID; |
| 32 | |
| 33 | if (!args.drop) { |
| 34 | return "Error: 'drop' must be provided."; |
| 35 | } |
| 36 | |
| 37 | let dropIds: number[] = []; |
| 38 | |
| 39 | try { |
| 40 | dropIds = parseRangeString(args.drop); |
| 41 | } catch (e) { |
| 42 | return `Error: Invalid range syntax. ${(e as Error).message}`; |
| 43 | } |
| 44 | |
| 45 | const allIds = [...new Set(dropIds)]; |
| 46 | |
| 47 | const allTags = getTagsBySession(deps.db, sessionId); |
| 48 | const foundSet = new Set(allTags.map((tag) => tag.tagNumber)); |
| 49 | const unknownIds = allIds.filter((id) => !foundSet.has(id)); |
| 50 | if (unknownIds.length > 0) { |
| 51 | return `Error: Unknown tag(s) ${formatIds(unknownIds)}. Check available tags in conversation.`; |
| 52 | } |
| 53 | |
| 54 | const activeTags = allTags.filter((tag) => tag.status === "active"); |
| 55 | const protectedTagIds = activeTags |
| 56 | .map((tag) => tag.tagNumber) |
| 57 | .sort((left, right) => right - left) |
| 58 | .slice(0, deps.protectedTags); |
| 59 | const protectedSet = new Set(protectedTagIds); |
| 60 | |
| 61 | const tagStatusMap = new Map(allTags.map((tag) => [tag.tagNumber, tag.status])); |
| 62 | |
| 63 | const pendingOps = getPendingOps(deps.db, sessionId); |
| 64 | const pendingMap = new Map(pendingOps.map((op) => [op.tagId, op.operation])); |
| 65 | |
| 66 | const conflicts: string[] = []; |
| 67 | for (const id of dropIds) { |
| 68 | if (tagStatusMap.get(id) === "compacted") { |
| 69 | conflicts.push(`§${id}§ is from before compaction`); |
| 70 | } |
| 71 | } |
| 72 | if (conflicts.length > 0) { |
| 73 | return `Error: Conflicting operations — ${conflicts.join("; ")}.`; |
| 74 | } |
| 75 | |
| 76 | const preFilterDropCount = dropIds.length; |
| 77 | dropIds = dropIds.filter( |
| 78 | (id) => tagStatusMap.get(id) !== "dropped" && pendingMap.get(id) !== "drop", |
| 79 | ); |
| 80 | const skippedCount = preFilterDropCount - dropIds.length; |
| 81 | |
| 82 | if (dropIds.length === 0) { |
| 83 | return "All requested tags were already queued or processed. No new action is needed."; |
| 84 | } |
| 85 | |
| 86 | try { |
| 87 | deps.db.transaction(() => { |
nothing calls this directly
no test coverage detected