(input: readonly ContentPart[], origin: PromptOrigin)
| 153 | } |
| 154 | |
| 155 | private launch(input: readonly ContentPart[], origin: PromptOrigin): number | null { |
| 156 | if (this.activeTurn) { |
| 157 | this.agent.emitEvent({ |
| 158 | type: 'error', |
| 159 | ...makeErrorPayload( |
| 160 | 'turn.agent_busy', |
| 161 | `Cannot launch a new turn while another turn (ID ${this.turnId}) is active`, |
| 162 | { details: { turnId: this.turnId } }, |
| 163 | ), |
| 164 | }); |
| 165 | return null; |
| 166 | } |
| 167 | |
| 168 | // While a manual/SDK compaction holds the context, defer the launch instead |
| 169 | // of rejecting it: buffer the input and replay it from `onCompactionFinished` |
| 170 | // once compaction's full lifecycle (summary + reinjection) completes. The |
| 171 | // deferred turn's eventual `turn.started` lets PromptService associate the |
| 172 | // pending prompt, so a prompt submitted mid-compaction completes normally |
| 173 | // rather than getting stuck "running". (Auto compaction runs inside an active |
| 174 | // turn, so the `activeTurn` check above already covers it.) |
| 175 | if (this.agent.fullCompaction.isCompacting) { |
| 176 | this.steerBuffer.push({ input, origin }); |
| 177 | return null; |
| 178 | } |
| 179 | |
| 180 | // Per-turn setup (telemetry, usage window, `turn.started`, appending the |
| 181 | // prompt) now lives in `runOneTurn`, so a goal-driven run emits a clean |
| 182 | // start/end pair per continuation turn rather than one mega-turn. |
| 183 | const turnId = this.allocateTurnId(); |
| 184 | const controller = new AbortController(); |
| 185 | const promise = this.turnWorker(turnId, input, origin, controller.signal); |
| 186 | const firstRequest = createControlledPromise<void>(); |
| 187 | this.activeTurn = { |
| 188 | turnId, |
| 189 | controller, |
| 190 | promise, |
| 191 | firstRequest, |
| 192 | }; |
| 193 | |
| 194 | void firstRequest.catch(() => undefined); |
| 195 | void promise.then(firstRequest.reject, firstRequest.reject); |
| 196 | |
| 197 | return turnId; |
| 198 | } |
| 199 | |
| 200 | /** Allocates the next monotonic turn id. */ |
| 201 | private allocateTurnId(): number { |
no test coverage detected