multi-message-queue-order.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import { runStreamCase, StreamEvent } from "../lib/stream-harness"
  2. const LONG_PROMPT =
  3. 'Run exactly this command and do not summarize until it finishes: sleep 6 && echo "done". After it finishes, reply with exactly "done".'
  4. const MESSAGE_ONE_PROMPT = 'For this follow-up, reply with only "ALPHA".'
  5. const MESSAGE_TWO_PROMPT = 'For this follow-up, reply with only "BETA".'
  6. async function main() {
  7. const startRequestId = `start-${Date.now()}`
  8. const firstMessageRequestId = `message-a-${Date.now()}`
  9. const secondMessageRequestId = `message-b-${Date.now()}`
  10. const shutdownRequestId = `shutdown-${Date.now()}`
  11. let initSeen = false
  12. let startAccepted = false
  13. let sentQueuedMessages = false
  14. let sentShutdown = false
  15. let firstMessageAccepted = false
  16. let secondMessageAccepted = false
  17. let firstMessageQueued = false
  18. let secondMessageQueued = false
  19. const resultOrder: string[] = []
  20. let queueDequeuedByFirst = false
  21. let queueDrainedBySecond = false
  22. let firstResultSeen = false
  23. let secondResultSeen = false
  24. await runStreamCase({
  25. timeoutMs: 180_000,
  26. onEvent(event: StreamEvent, context) {
  27. if (event.type === "system" && event.subtype === "init" && !initSeen) {
  28. initSeen = true
  29. context.sendCommand({
  30. command: "start",
  31. requestId: startRequestId,
  32. prompt: LONG_PROMPT,
  33. })
  34. return
  35. }
  36. if (
  37. event.type === "control" &&
  38. event.subtype === "ack" &&
  39. event.command === "start" &&
  40. event.requestId === startRequestId &&
  41. !startAccepted
  42. ) {
  43. startAccepted = true
  44. context.sendCommand({
  45. command: "message",
  46. requestId: firstMessageRequestId,
  47. prompt: MESSAGE_ONE_PROMPT,
  48. })
  49. context.sendCommand({
  50. command: "message",
  51. requestId: secondMessageRequestId,
  52. prompt: MESSAGE_TWO_PROMPT,
  53. })
  54. sentQueuedMessages = true
  55. return
  56. }
  57. if (
  58. event.type === "control" &&
  59. event.subtype === "ack" &&
  60. event.command === "message" &&
  61. event.requestId === firstMessageRequestId
  62. ) {
  63. firstMessageAccepted = true
  64. return
  65. }
  66. if (
  67. event.type === "control" &&
  68. event.subtype === "ack" &&
  69. event.command === "message" &&
  70. event.requestId === secondMessageRequestId
  71. ) {
  72. secondMessageAccepted = true
  73. return
  74. }
  75. if (
  76. event.type === "control" &&
  77. event.subtype === "done" &&
  78. event.command === "message" &&
  79. event.requestId === firstMessageRequestId &&
  80. event.code === "queued"
  81. ) {
  82. firstMessageQueued = true
  83. return
  84. }
  85. if (
  86. event.type === "control" &&
  87. event.subtype === "done" &&
  88. event.command === "message" &&
  89. event.requestId === secondMessageRequestId &&
  90. event.code === "queued"
  91. ) {
  92. secondMessageQueued = true
  93. return
  94. }
  95. if (
  96. event.type === "queue" &&
  97. event.subtype === "dequeued" &&
  98. event.requestId === firstMessageRequestId &&
  99. event.queueDepth === 1
  100. ) {
  101. queueDequeuedByFirst = true
  102. return
  103. }
  104. if (
  105. event.type === "queue" &&
  106. event.subtype === "drained" &&
  107. event.requestId === secondMessageRequestId &&
  108. event.queueDepth === 0
  109. ) {
  110. queueDrainedBySecond = true
  111. return
  112. }
  113. if (event.type === "result" && event.done === true) {
  114. if (event.requestId === firstMessageRequestId) {
  115. firstResultSeen = true
  116. resultOrder.push(firstMessageRequestId)
  117. }
  118. if (event.requestId === secondMessageRequestId) {
  119. secondResultSeen = true
  120. resultOrder.push(secondMessageRequestId)
  121. }
  122. }
  123. if (!firstResultSeen || !secondResultSeen || sentShutdown) {
  124. return
  125. }
  126. const expectedOrder = [firstMessageRequestId, secondMessageRequestId].join(",")
  127. if (resultOrder.join(",") !== expectedOrder) {
  128. throw new Error(
  129. `queued message result order mismatch; expected=${expectedOrder} actual=${resultOrder.join(",")}`,
  130. )
  131. }
  132. context.sendCommand({
  133. command: "shutdown",
  134. requestId: shutdownRequestId,
  135. })
  136. sentShutdown = true
  137. },
  138. onTimeoutMessage() {
  139. return `timed out waiting for queued message order validation (initSeen=${initSeen}, startAccepted=${startAccepted}, sentQueuedMessages=${sentQueuedMessages}, firstMessageAccepted=${firstMessageAccepted}, secondMessageAccepted=${secondMessageAccepted}, firstMessageQueued=${firstMessageQueued}, secondMessageQueued=${secondMessageQueued}, queueDequeuedByFirst=${queueDequeuedByFirst}, queueDrainedBySecond=${queueDrainedBySecond}, resultOrder=${resultOrder.join(" -> ")}, firstResultSeen=${firstResultSeen}, secondResultSeen=${secondResultSeen})`
  140. },
  141. })
  142. if (
  143. !firstMessageAccepted ||
  144. !secondMessageAccepted ||
  145. !firstMessageQueued ||
  146. !secondMessageQueued ||
  147. !queueDequeuedByFirst ||
  148. !queueDrainedBySecond
  149. ) {
  150. throw new Error(
  151. `expected both queued messages to be accepted/queued and queue transitions observed (firstMessageAccepted=${firstMessageAccepted}, secondMessageAccepted=${secondMessageAccepted}, firstMessageQueued=${firstMessageQueued}, secondMessageQueued=${secondMessageQueued}, queueDequeuedByFirst=${queueDequeuedByFirst}, queueDrainedBySecond=${queueDrainedBySecond})`,
  152. )
  153. }
  154. const expectedOrder = [firstMessageRequestId, secondMessageRequestId].join(",")
  155. if (resultOrder.join(",") !== expectedOrder) {
  156. throw new Error(
  157. `queued message result order mismatch; expected=${expectedOrder} actual=${resultOrder.join(",")}`,
  158. )
  159. }
  160. }
  161. main().catch((error) => {
  162. console.error(`[FAIL] ${error instanceof Error ? error.message : String(error)}`)
  163. process.exit(1)
  164. })