(ctx: SweepCommandContext)
| 127 | } |
| 128 | |
| 129 | export async function handleSweepCommand(ctx: SweepCommandContext): Promise<void> { |
| 130 | const { client, state, config, logger, sessionId, messages, args, workingDirectory } = ctx |
| 131 | |
| 132 | const params = getCurrentParams(state, messages, logger) |
| 133 | const protectedTools = config.commands.protectedTools |
| 134 | |
| 135 | syncToolCache(state, config, logger, messages) |
| 136 | buildToolIdList(state, messages) |
| 137 | |
| 138 | // Parse optional numeric argument |
| 139 | const numArg = args[0] ? parseInt(args[0], 10) : null |
| 140 | const isLastNMode = numArg !== null && !isNaN(numArg) && numArg > 0 |
| 141 | |
| 142 | let toolIdsToSweep: string[] |
| 143 | let mode: "since-user" | "last-n" |
| 144 | |
| 145 | if (isLastNMode) { |
| 146 | // Mode: Sweep last N tools |
| 147 | mode = "last-n" |
| 148 | const startIndex = Math.max(0, state.toolIdList.length - numArg!) |
| 149 | toolIdsToSweep = state.toolIdList.slice(startIndex) |
| 150 | logger.info(`Sweep command: last ${numArg} mode, found ${toolIdsToSweep.length} tools`) |
| 151 | } else { |
| 152 | // Mode: Sweep since last user message |
| 153 | mode = "since-user" |
| 154 | const lastUserMsgIndex = findLastUserMessageIndex(messages) |
| 155 | |
| 156 | if (lastUserMsgIndex === -1) { |
| 157 | // No user message found - show message and return |
| 158 | const message = formatNoUserMessage() |
| 159 | await sendIgnoredMessage(client, sessionId, message, params, logger) |
| 160 | logger.info("Sweep command: no user message found") |
| 161 | return |
| 162 | } else { |
| 163 | toolIdsToSweep = collectToolIdsAfterIndex(state, messages, lastUserMsgIndex) |
| 164 | logger.info( |
| 165 | `Sweep command: found last user at index ${lastUserMsgIndex}, sweeping ${toolIdsToSweep.length} tools`, |
| 166 | ) |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | // Filter out already-pruned tools, protected tools, and protected file paths |
| 171 | const newToolIds = toolIdsToSweep.filter((id) => { |
| 172 | if (state.prune.tools.has(id)) { |
| 173 | return false |
| 174 | } |
| 175 | const entry = state.toolParameters.get(id) |
| 176 | if (!entry) { |
| 177 | return true |
| 178 | } |
| 179 | if (isToolNameProtected(entry.tool, protectedTools)) { |
| 180 | logger.debug(`Sweep: skipping protected tool ${entry.tool} (${id})`) |
| 181 | return false |
| 182 | } |
| 183 | const filePaths = getFilePathsFromParameters(entry.tool, entry.parameters) |
| 184 | if (isFilePathProtected(filePaths, config.protectedFilePatterns)) { |
| 185 | logger.debug(`Sweep: skipping protected file path(s) ${filePaths.join(", ")} (${id})`) |
| 186 | return false |
no test coverage detected