/**
 * SlackLiveSender — the real Slack `SurfaceSender` for the live-test harness
 * (docs/specs/live-user-channel-proof-standard.md §5.4). It posts a message into a
 * Slack channel AS A NON-AGENT IDENTITY (a user/second-bot token, NOT Echo's own bot
 * — Echo never replies to itself) and then waits for the AGENT's reply by polling the
 * channel history for a message from the agent's bot user id.
 *
 * Parameterization is deliberate: the sender takes an already-constructed
 * `SlackApiClient` (carrying the non-Echo sender token) + the agent's bot user id. So
 * the CODE is complete and unit-testable here; the only thing that has to be provided
 * at wiring time is the sender CREDENTIAL (a user token / second-bot token in the
 * demo workspace). That credential is the one piece that may need provisioning — the
 * logic does not.
 *
 * `awaitReply` identifies the agent's reply DETERMINISTICALLY (a message strictly
 * after the sent ts whose author is the agent's bot user id), never a fuzzy guess, and
 * resolves null on timeout (the harness records that as a FAIL with reason). The
 * responder-MACHINE attribution (the cross-machine proof) is NOT done here — that is
 * the RealChannelDriver's injected placement reader; this sender only returns the
 * reply text + id.
 */
export class SlackLiveSender {
    d;
    constructor(deps) { this.d = deps; }
    now() { return (this.d.now ?? Date.now)(); }
    async sleep(ms) {
        return (this.d.sleep ?? ((m) => new Promise((r) => setTimeout(r, m))))(ms);
    }
    log(m) { this.d.logger?.(`[slack-live-sender] ${m}`); }
    async send(channelId, text) {
        const res = await this.d.api.call('chat.postMessage', { channel: channelId, text });
        if (!res.ts) {
            // A post with no ts is a real failure — surface it (the harness records a driver
            // error FAIL), never fabricate a messageId.
            throw new Error(`chat.postMessage returned no ts (ok=${res.ok}) — message not posted`);
        }
        return { messageId: res.ts };
    }
    async awaitReply(channelId, opts) {
        const deadline = this.now() + opts.timeoutMs;
        const pollMs = this.d.pollIntervalMs ?? 2000;
        const after = opts.afterMessageId; // a Slack ts string; lexicographic compare works for ts
        // Poll at least once even if timeoutMs is ~0.
        for (let first = true; first || this.now() < deadline; first = false) {
            const reply = await this.findAgentReply(channelId, after);
            if (reply)
                return reply;
            if (this.now() >= deadline)
                break;
            await this.sleep(pollMs);
        }
        this.log(`no agent reply in ${channelId} within ${opts.timeoutMs}ms`);
        return null;
    }
    /** Find the FIRST agent-authored message strictly after `after` (oldest-first). */
    async findAgentReply(channelId, after) {
        const res = await this.d.api.call('conversations.history', {
            channel: channelId,
            ...(after ? { oldest: after, inclusive: false } : {}),
            limit: 100,
        });
        const messages = res.messages ?? [];
        // conversations.history returns newest-first; scan oldest-first so we return the
        // EARLIEST agent reply after the prompt (deterministic).
        const oldestFirst = [...messages].sort((a, b) => (a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0));
        for (const m of oldestFirst) {
            if (after && !(m.ts > after))
                continue; // strictly-after guard (belt + suspenders vs `oldest`)
            if (m.user !== this.d.agentBotUserId)
                continue; // only the AGENT's reply
            const text = m.text ?? '';
            return { text, messageId: m.ts };
        }
        return null;
    }
}
//# sourceMappingURL=SlackLiveSender.js.map