@@ -141,6 +141,65 @@ async function waitForPath(filePath: string, timeoutMs = 60_000): Promise<void>
|
141 | 141 | throw new Error(`timed out waiting for ${filePath}`); |
142 | 142 | } |
143 | 143 | |
| 144 | +function extractAssistantTexts(messages: unknown[]): string[] { |
| 145 | +const texts: string[] = []; |
| 146 | +for (const entry of messages) { |
| 147 | +if (!entry || typeof entry !== "object") { |
| 148 | +continue; |
| 149 | +} |
| 150 | +if ((entry as { role?: unknown }).role !== "assistant") { |
| 151 | +continue; |
| 152 | +} |
| 153 | +const text = extractFirstTextBlock(entry); |
| 154 | +if (typeof text === "string" && text.trim().length > 0) { |
| 155 | +texts.push(text); |
| 156 | +} |
| 157 | +} |
| 158 | +return texts; |
| 159 | +} |
| 160 | + |
| 161 | +function formatAssistantTextPreview(texts: string[], maxChars = 800): string { |
| 162 | +const combined = texts.join("\n\n").trim(); |
| 163 | +if (!combined) { |
| 164 | +return "<none>"; |
| 165 | +} |
| 166 | +return combined.length > maxChars ? `${combined.slice(0, maxChars)}...` : combined; |
| 167 | +} |
| 168 | + |
| 169 | +async function waitForAssistantText(params: { |
| 170 | +client: GatewayClient; |
| 171 | +contains: string; |
| 172 | +sessionKey: string; |
| 173 | +timeoutMs?: number; |
| 174 | +}): Promise<string> { |
| 175 | +const timeoutMs = params.timeoutMs ?? 60_000; |
| 176 | +const startedAt = Date.now(); |
| 177 | +while (Date.now() - startedAt < timeoutMs) { |
| 178 | +const history: { messages?: unknown[] } = await params.client.request("chat.history", { |
| 179 | +sessionKey: params.sessionKey, |
| 180 | +limit: 24, |
| 181 | +}); |
| 182 | +const assistantTexts = extractAssistantTexts(history.messages ?? []); |
| 183 | +const matched = assistantTexts.find((text) => text.includes(params.contains)); |
| 184 | +if (matched) { |
| 185 | +return matched; |
| 186 | +} |
| 187 | +await new Promise((resolve) => { |
| 188 | +setTimeout(resolve, 500); |
| 189 | +}); |
| 190 | +} |
| 191 | + |
| 192 | +const finalHistory: { messages?: unknown[] } = await params.client.request("chat.history", { |
| 193 | +sessionKey: params.sessionKey, |
| 194 | +limit: 24, |
| 195 | +}); |
| 196 | +throw new Error( |
| 197 | +`timed out waiting for assistant text containing ${params.contains}: ${formatAssistantTextPreview( |
| 198 | + extractAssistantTexts(finalHistory.messages ?? []), |
| 199 | + )}`, |
| 200 | +); |
| 201 | +} |
| 202 | + |
144 | 203 | async function approveTrajectoryExport(client: GatewayClient): Promise<string> { |
145 | 204 | const approvals = (await client.request( |
146 | 205 | "exec.approval.list", |
@@ -282,7 +341,12 @@ describeLive("gateway live trajectory export", () => {
|
282 | 341 | const finalText = |
283 | 342 | typeof exportResponse?.message === "object" |
284 | 343 | ? extractFirstTextBlock(exportResponse.message) |
285 | | - : undefined; |
| 344 | + : await waitForAssistantText({ |
| 345 | + client, |
| 346 | + sessionKey, |
| 347 | +contains: "Trajectory exports can include", |
| 348 | +timeoutMs: 60_000, |
| 349 | +}); |
286 | 350 | expect(finalText).toContain("Trajectory exports can include"); |
287 | 351 | expect(finalText).toContain("through exec approval"); |
288 | 352 | const approvalId = await approveTrajectoryExport(client); |
|