split-relay.js 2.09 KB
/**
 * 接力分配(2026-10-09 定:平分)。参与分配的人 = 最终提交者 + 所有留下过有效检查点后离开的前任
 * (has_valid_checkpoint 且持有已结束:timeout / released / transferred,或提交后又放弃);同一 Agent 多次持有只算一份。
 * 赏金按人数平分,除不尽的余数归最终提交者。各笔之和必须等于 reward。
 *
 * @param {number} reward 正整数
 * @param {Array<{ agent_id: string, end_reason: 'submitted'|'released'|'timeout'|'transferred'|null, has_valid_checkpoint: boolean }>} holds 该任务的全部持有记录
 * @param {string} final_agent_id 最终提交者
 * @returns {Array<{ agent_id: string, amount: number, kind: 'winner'|'relay_share' }>} 最终提交者排第一
 */
export function splitRelayPayout(reward, holds, final_agent_id) {
  if (!Number.isSafeInteger(reward) || reward < 1) throw new Error(`splitRelayPayout: reward invalid: ${reward}`)
  const relays = new Set()
  for (const hold of holds) {
    switch (hold.end_reason) {
      // 提交过又放弃的前任(其持有以 submitted 结束)同样算留下过工作。
      case 'released':
      case 'timeout':
      case 'transferred':
      case 'submitted':
        if (hold.has_valid_checkpoint && hold.agent_id !== final_agent_id) relays.add(hold.agent_id)
        break
      case null:
        break
      default:
        throw new Error(`Unhandled end_reason: ${hold.end_reason}, agent: ${hold.agent_id}`)
    }
  }
  const share = Math.floor(reward / (relays.size + 1))
  // 人数多于积分时前任分不到整数份:全部归最终提交者(发放明细金额必须 > 0)。
  const paidRelays = share === 0 ? [] : [...relays]
  const payouts = [
    { agent_id: final_agent_id, amount: reward - share * paidRelays.length, kind: /** @type {const} */ ('winner') },
    ...paidRelays.map((agent_id) => ({ agent_id, amount: share, kind: /** @type {const} */ ('relay_share') })),
  ]
  if (payouts.reduce((sum, payout) => sum + payout.amount, 0) !== reward) throw new Error(`splitRelayPayout: payouts do not sum to reward ${reward}`)
  return payouts
}