( type: ClineAsk, text?: string, partial?: boolean, progressStatus?: ToolProgressStatus, isProtected?: boolean, )
| 1149 | // false (completion of partial message), undefined (individual complete |
| 1150 | // message). |
| 1151 | async ask( |
| 1152 | type: ClineAsk, |
| 1153 | text?: string, |
| 1154 | partial?: boolean, |
| 1155 | progressStatus?: ToolProgressStatus, |
| 1156 | isProtected?: boolean, |
| 1157 | ): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> { |
| 1158 | // If this Cline instance was aborted by the provider, then the only |
| 1159 | // thing keeping us alive is a promise still running in the background, |
| 1160 | // in which case we don't want to send its result to the webview as it |
| 1161 | // is attached to a new instance of Cline now. So we can safely ignore |
| 1162 | // the result of any active promises, and this class will be |
| 1163 | // deallocated. (Although we set Cline = undefined in provider, that |
| 1164 | // simply removes the reference to this instance, but the instance is |
| 1165 | // still alive until this promise resolves or rejects.) |
| 1166 | if (this.abort) { |
| 1167 | throw new Error(`[RooCode#ask] task ${this.taskId}.${this.instanceId} aborted`) |
| 1168 | } |
| 1169 | |
| 1170 | let askTs: number |
| 1171 | |
| 1172 | // Resolve auto-approval before adding the message so the state snapshot |
| 1173 | // sent to the webview already carries isAnswered:true when the ask will |
| 1174 | // be immediately resolved. This eliminates the race between the state |
| 1175 | // update (which shows approval buttons) and the former separate |
| 1176 | // clearApprovalButtons message (which could arrive before buttons were |
| 1177 | // rendered, leaving them stuck on-screen). |
| 1178 | const provider = this.providerRef.deref() |
| 1179 | const state = provider ? await provider.getState() : undefined |
| 1180 | const approval = await checkAutoApproval({ state, ask: type, text, isProtected }) |
| 1181 | const isAutoAnswered = approval.decision === "approve" || approval.decision === "deny" |
| 1182 | |
| 1183 | if (partial !== undefined) { |
| 1184 | const lastMessage = this.clineMessages.at(-1) |
| 1185 | |
| 1186 | const isUpdatingPreviousPartial = |
| 1187 | lastMessage && lastMessage.partial && lastMessage.type === "ask" && lastMessage.ask === type |
| 1188 | |
| 1189 | if (partial) { |
| 1190 | if (isUpdatingPreviousPartial) { |
| 1191 | // Existing partial message, so update it. |
| 1192 | lastMessage.text = text |
| 1193 | lastMessage.partial = partial |
| 1194 | lastMessage.progressStatus = progressStatus |
| 1195 | lastMessage.isProtected = isProtected |
| 1196 | // TODO: Be more efficient about saving and posting only new |
| 1197 | // data or one whole message at a time so ignore partial for |
| 1198 | // saves, and only post parts of partial message instead of |
| 1199 | // whole array in new listener. |
| 1200 | // Fire-and-forget: the webview post is internally guarded, but |
| 1201 | // the `RooCodeEventName.Message` emit can synchronously throw |
| 1202 | // if any consumer-attached listener does, which would surface |
| 1203 | // here as an unhandled rejection. Log it instead. |
| 1204 | this.updateClineMessage(lastMessage).catch((error) => { |
| 1205 | console.error("[Task#ask] updateClineMessage failed:", error) |
| 1206 | }) |
| 1207 | // console.log("Task#ask: current ask promise was ignored (#1)") |
| 1208 | throw new AskIgnoredError("updating existing partial") |
no test coverage detected