(spec: E2ECase)
| 87 | } |
| 88 | |
| 89 | async function runCase(spec: E2ECase): Promise<CaseResult> { |
| 90 | const errors: string[] = []; |
| 91 | const samples: CaseResult["samples"] = []; |
| 92 | const t0 = Date.now(); |
| 93 | |
| 94 | // Mark "now" in the channel timeline so we know which bot replies are ours. |
| 95 | const beforeHist = await channelHistory(TEST_CHANNEL, 1); |
| 96 | const sinceTs = beforeHist[0]?.ts ?? "0"; |
| 97 | |
| 98 | // Send the prompt as Atai. |
| 99 | const sent = await postAsUser(TEST_CHANNEL, spec.prompt); |
| 100 | const parentTs = (sent as { ts?: string }).ts ?? ""; |
| 101 | if (!parentTs) errors.push("postAsUser returned no ts"); |
| 102 | |
| 103 | // The bot's reply for an @mention goes into a thread off parentTs; for |
| 104 | // a /agent slash command it lands flat in the channel. We can't tell |
| 105 | // from the prompt alone — try the thread first, then fall back to flat. |
| 106 | const flatMode = /^\/agent\b/.test(spec.prompt); |
| 107 | const sampleIntervalMs = spec.sampleIntervalMs ?? 1000; |
| 108 | const maxWaitMs = spec.maxWaitMs ?? 30_000; |
| 109 | let followUpResult: CaseResult | undefined; |
| 110 | |
| 111 | // Schedule a mid-stream interrupt if the case asks for one. |
| 112 | let interruptTimer: NodeJS.Timeout | undefined; |
| 113 | if (spec.interrupt && parentTs) { |
| 114 | interruptTimer = setTimeout(() => { |
| 115 | postAsUser(TEST_CHANNEL, spec.interrupt!.prompt, { |
| 116 | threadTs: parentTs, |
| 117 | }).catch((e: Error) => |
| 118 | errors.push(`interrupt send failed: ${e.message}`), |
| 119 | ); |
| 120 | }, spec.interrupt.afterMs); |
| 121 | } |
| 122 | |
| 123 | const onSample = (s: { elapsedMs: number; text: string | undefined }) => { |
| 124 | const text = s.text ?? ""; |
| 125 | const balanced = isBalanced(text); |
| 126 | samples.push({ |
| 127 | elapsedMs: s.elapsedMs, |
| 128 | balanced, |
| 129 | len: text.length, |
| 130 | preview: text.slice(0, 100), |
| 131 | // Capture full text for unbalanced samples so we can diagnose without |
| 132 | // having to re-run. Skipped for balanced samples to keep reports small. |
| 133 | ...(text.length > 0 && !balanced ? { full: text } : {}), |
| 134 | }); |
| 135 | }; |
| 136 | |
| 137 | if (interruptTimer === undefined) { |
| 138 | /* no-op */ |
| 139 | } |
| 140 | const result = flatMode |
| 141 | ? await watchForChannelReply({ |
| 142 | channel: TEST_CHANNEL, |
| 143 | sinceTs, |
| 144 | intervalMs: sampleIntervalMs, |
| 145 | timeoutMs: maxWaitMs, |
| 146 | onSample, |
no test coverage detected