@alexkroman1/aai-cli 5.13.2 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/dist/{_agent-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
  2. package/dist/_agent.d.ts +18 -0
  3. package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
  4. package/dist/_api-client.d.ts +39 -0
  5. package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
  6. package/dist/_bundler.d.ts +43 -5
  7. package/dist/_cli-common.d.ts +67 -3
  8. package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
  9. package/dist/_deploy.d.ts +2 -5
  10. package/dist/_dev-restart.d.ts +6 -1
  11. package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
  12. package/dist/_dev-server.d.ts +30 -2
  13. package/dist/_e2e-test-utils.d.ts +80 -0
  14. package/dist/_fault-mode.d.ts +175 -0
  15. package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
  16. package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
  17. package/dist/_output.d.ts +9 -2
  18. package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
  19. package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
  20. package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
  21. package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
  22. package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
  23. package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
  24. package/dist/_utils.d.ts +18 -2
  25. package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
  26. package/dist/_vite-env.d.ts +30 -0
  27. package/dist/build-BO1Ni6oJ.mjs +62 -0
  28. package/dist/build.d.ts +19 -0
  29. package/dist/cli-workflow.d.ts +10 -0
  30. package/dist/cli.d.ts +1 -2
  31. package/dist/cli.mjs +406 -224
  32. package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
  33. package/dist/client-bundler.mjs +1 -1
  34. package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
  35. package/dist/delete.d.ts +2 -8
  36. package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
  37. package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
  38. package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
  39. package/dist/eject.d.ts +30 -3
  40. package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
  41. package/dist/init.d.ts +11 -0
  42. package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
  43. package/dist/project-config.mjs +1 -1
  44. package/dist/scaffold/CLAUDE.md +498 -91
  45. package/dist/scaffold/package.json +11 -8
  46. package/dist/scaffold/pnpm-workspace.yaml +32 -0
  47. package/dist/scaffold/server.mjs +53 -57
  48. package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
  49. package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
  50. package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
  51. package/dist/templates/code-interpreter/agent.ts +0 -2
  52. package/dist/templates/dispatch-center/agent.test.ts +21 -30
  53. package/dist/templates/dispatch-center/agent.ts +4 -31
  54. package/dist/templates/dispatch-center/client.tsx +41 -28
  55. package/dist/templates/dispatch-center/shared.ts +73 -66
  56. package/dist/templates/dispatch-center/tools/incident_add_note.ts +13 -15
  57. package/dist/templates/dispatch-center/tools/incident_create.ts +42 -45
  58. package/dist/templates/dispatch-center/tools/incident_escalate.ts +60 -62
  59. package/dist/templates/dispatch-center/tools/incident_get.ts +10 -6
  60. package/dist/templates/dispatch-center/tools/incident_triage.ts +44 -46
  61. package/dist/templates/dispatch-center/tools/incident_update_status.ts +40 -42
  62. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +3 -6
  63. package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +21 -24
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +53 -55
  66. package/dist/templates/dispatch-center/tools/resources_get_available.ts +3 -5
  67. package/dist/templates/dispatch-center/tools/resources_update_status.ts +40 -43
  68. package/dist/templates/embedded-assets/agent.test.ts +83 -0
  69. package/dist/templates/embedded-assets/agent.ts +1 -48
  70. package/dist/templates/embedded-assets/shared.ts +17 -0
  71. package/dist/templates/embedded-assets/system-prompt.md +13 -0
  72. package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
  73. package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
  74. package/dist/templates/health-assistant/agent.test.ts +169 -0
  75. package/dist/templates/health-assistant/agent.ts +1 -166
  76. package/dist/templates/health-assistant/fda.ts +100 -0
  77. package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
  78. package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
  79. package/dist/templates/infocom-adventure/agent.test.ts +205 -0
  80. package/dist/templates/infocom-adventure/agent.ts +3 -106
  81. package/dist/templates/infocom-adventure/client.tsx +32 -28
  82. package/dist/templates/infocom-adventure/shared.ts +36 -16
  83. package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
  84. package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
  85. package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
  86. package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
  87. package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
  88. package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
  89. package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
  90. package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
  91. package/dist/templates/link-digest/agent.test.ts +223 -0
  92. package/dist/templates/link-digest/agent.ts +79 -0
  93. package/dist/templates/link-digest/client.tsx +146 -0
  94. package/dist/templates/link-digest/workflows/digest.ts +248 -0
  95. package/dist/templates/math-buddy/agent.ts +0 -2
  96. package/dist/templates/night-owl/agent.test.ts +80 -0
  97. package/dist/templates/night-owl/agent.ts +1 -68
  98. package/dist/templates/night-owl/system-prompt.md +10 -0
  99. package/dist/templates/night-owl/tools/recommend.ts +64 -0
  100. package/dist/templates/personal-finance/agent.ts +0 -2
  101. package/dist/templates/pizza-ordering/agent.test.ts +59 -58
  102. package/dist/templates/pizza-ordering/agent.ts +10 -159
  103. package/dist/templates/pizza-ordering/client.tsx +4 -4
  104. package/dist/templates/pizza-ordering/shared.ts +44 -27
  105. package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
  106. package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
  107. package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
  108. package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
  109. package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
  110. package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
  111. package/dist/templates/plan-and-execute/agent.test.ts +411 -0
  112. package/dist/templates/plan-and-execute/agent.ts +23 -0
  113. package/dist/templates/plan-and-execute/client.tsx +115 -0
  114. package/dist/templates/plan-and-execute/graph.ts +178 -0
  115. package/dist/templates/plan-and-execute/prompts.ts +110 -0
  116. package/dist/templates/plan-and-execute/shared.ts +161 -0
  117. package/dist/templates/plan-and-execute/system-prompt.md +18 -0
  118. package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
  119. package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
  120. package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
  121. package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
  122. package/dist/templates/recap-workflow/agent.test.ts +762 -0
  123. package/dist/templates/recap-workflow/agent.ts +95 -0
  124. package/dist/templates/recap-workflow/shared.ts +43 -0
  125. package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
  126. package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
  127. package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
  128. package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
  129. package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
  130. package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
  131. package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
  132. package/dist/templates/redline/agent.test.ts +222 -0
  133. package/dist/templates/redline/agent.ts +89 -0
  134. package/dist/templates/redline/client.tsx +184 -0
  135. package/dist/templates/redline/workflows/prompts.ts +68 -0
  136. package/dist/templates/redline/workflows/redline.ts +258 -0
  137. package/dist/templates/research-workflow/agent.test.ts +525 -0
  138. package/dist/templates/research-workflow/agent.ts +94 -0
  139. package/dist/templates/research-workflow/shared.ts +35 -0
  140. package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
  141. package/dist/templates/research-workflow/tools/request_research.ts +33 -0
  142. package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
  143. package/dist/templates/research-workflow/tools/research_status.ts +40 -0
  144. package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
  145. package/dist/templates/research-workflow/workflows/research.ts +558 -0
  146. package/dist/templates/retail/agent.test.ts +319 -256
  147. package/dist/templates/retail/agent.ts +6 -46
  148. package/dist/templates/retail/authenticate.ts +2 -2
  149. package/dist/templates/retail/client.tsx +45 -24
  150. package/dist/templates/retail/registry.test.ts +40 -25
  151. package/dist/templates/retail/resolve.test.ts +17 -16
  152. package/dist/templates/retail/resolve.ts +32 -67
  153. package/dist/templates/retail/shared.test.ts +21 -19
  154. package/dist/templates/retail/shared.ts +61 -23
  155. package/dist/templates/retail/store.test.ts +47 -52
  156. package/dist/templates/retail/store.ts +92 -79
  157. package/dist/templates/retail/swap.test.ts +20 -17
  158. package/dist/templates/retail/swap.ts +7 -14
  159. package/dist/templates/retail/tools/cancel_pending_order.ts +7 -11
  160. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +26 -14
  161. package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
  162. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
  163. package/dist/templates/retail/tools/get_item_details.ts +7 -8
  164. package/dist/templates/retail/tools/get_order_details.ts +7 -8
  165. package/dist/templates/retail/tools/get_product_details.ts +6 -8
  166. package/dist/templates/retail/tools/get_user_details.ts +6 -8
  167. package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
  168. package/dist/templates/retail/tools/modify_pending_order_address.ts +6 -10
  169. package/dist/templates/retail/tools/modify_pending_order_items.ts +8 -20
  170. package/dist/templates/retail/tools/modify_pending_order_payment.ts +7 -13
  171. package/dist/templates/retail/tools/modify_user_address.ts +6 -10
  172. package/dist/templates/retail/tools/return_delivered_order_items.ts +7 -13
  173. package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
  174. package/dist/templates/solo-rpg/agent.test.ts +169 -54
  175. package/dist/templates/solo-rpg/agent.ts +4 -22
  176. package/dist/templates/solo-rpg/client.tsx +19 -6
  177. package/dist/templates/solo-rpg/shared.ts +20 -20
  178. package/dist/templates/solo-rpg/tools/action_roll.ts +3 -5
  179. package/dist/templates/solo-rpg/tools/burn_momentum.ts +3 -5
  180. package/dist/templates/solo-rpg/tools/check_state.ts +7 -7
  181. package/dist/templates/solo-rpg/tools/load_game.ts +3 -3
  182. package/dist/templates/solo-rpg/tools/oracle.ts +19 -12
  183. package/dist/templates/solo-rpg/tools/save_game.ts +11 -8
  184. package/dist/templates/solo-rpg/tools/setup_character.ts +3 -3
  185. package/dist/templates/solo-rpg/tools/update_state.ts +13 -13
  186. package/dist/templates/support-line/agent.test.ts +343 -0
  187. package/dist/templates/support-line/agent.ts +27 -0
  188. package/dist/templates/support-line/client.tsx +122 -0
  189. package/dist/templates/support-line/graph.ts +224 -0
  190. package/dist/templates/support-line/knowledge.json +65 -0
  191. package/dist/templates/support-line/prompts.ts +95 -0
  192. package/dist/templates/support-line/shared.ts +240 -0
  193. package/dist/templates/support-line/system-prompt.md +17 -0
  194. package/dist/templates/support-line/tools/answer_question.ts +72 -0
  195. package/dist/templates/support-line/tools/list_topics.ts +22 -0
  196. package/dist/templates/support-line/tools/log_ticket.ts +30 -0
  197. package/dist/templates/transcription-workflow/agent.test.ts +563 -0
  198. package/dist/templates/transcription-workflow/agent.ts +92 -0
  199. package/dist/templates/transcription-workflow/client.tsx +234 -0
  200. package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
  201. package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
  202. package/dist/templates/travel-concierge/agent.test.ts +338 -0
  203. package/dist/templates/travel-concierge/agent.ts +29 -0
  204. package/dist/templates/travel-concierge/client.tsx +161 -0
  205. package/dist/templates/travel-concierge/routing.ts +138 -0
  206. package/dist/templates/travel-concierge/shared.ts +547 -0
  207. package/dist/templates/travel-concierge/system-prompt.md +24 -0
  208. package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
  209. package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
  210. package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
  211. package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
  212. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
  213. package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
  214. package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
  215. package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
  216. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
  217. package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
  218. package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
  219. package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
  220. package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
  221. package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
  222. package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
  223. package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
  224. package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
  225. package/dist/templates/web-researcher/agent.ts +0 -2
  226. package/dist/templates/web-researcher/system-prompt.md +10 -0
  227. package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
  228. package/dist/typecheck.mjs +17 -15
  229. package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
  230. package/dist/worker-bundler.d.ts +14 -0
  231. package/dist/worker-bundler.mjs +1 -108
  232. package/dist/workflow-B-O97AqI.mjs +125 -0
  233. package/dist/workflow-bundler.d.ts +133 -0
  234. package/dist/workflow.d.ts +67 -0
  235. package/package.json +13 -9
  236. package/dist/build-DQJ1ZSm9.mjs +0 -39
@@ -1,101 +1,116 @@
1
1
  import type { ToolContext } from "@alexkroman1/aai";
2
+ import { isToolFailure } from "@alexkroman1/aai";
3
+ import { createToolContext } from "@alexkroman1/aai/testing";
2
4
  import { describe, expect, test } from "vitest";
3
5
  import type { AuthResult } from "./authenticate.ts";
4
6
  import type { Address } from "./shared.ts";
5
- import type { ErrorResult } from "./store.ts";
6
- import { getState, isError } from "./store.ts";
7
- import { cancelPendingOrder } from "./tools/cancel_pending_order.ts";
8
- import { exchangeDeliveredOrderItems } from "./tools/exchange_delivered_order_items.ts";
9
- import { findUserIdByEmail } from "./tools/find_user_id_by_email.ts";
10
- import { findUserIdByNameZip } from "./tools/find_user_id_by_name_zip.ts";
11
- import { getItemDetails } from "./tools/get_item_details.ts";
12
- import { getOrderDetails } from "./tools/get_order_details.ts";
13
- import { getProductDetails } from "./tools/get_product_details.ts";
14
- import { getUserDetails } from "./tools/get_user_details.ts";
15
- import { listAllProductTypes } from "./tools/list_all_product_types.ts";
16
- import { modifyPendingOrderAddress } from "./tools/modify_pending_order_address.ts";
17
- import { modifyPendingOrderItems } from "./tools/modify_pending_order_items.ts";
18
- import { modifyPendingOrderPayment } from "./tools/modify_pending_order_payment.ts";
19
- import { modifyUserAddress } from "./tools/modify_user_address.ts";
20
- import { returnDeliveredOrderItems } from "./tools/return_delivered_order_items.ts";
21
- import { transferToHumanAgents } from "./tools/transfer_to_human_agents.ts";
22
-
23
- let sessionCounter = 0;
24
-
7
+ import { retailSlot } from "./store.ts";
8
+ import cancelPendingOrder from "./tools/cancel_pending_order.ts";
9
+ import exchangeDeliveredOrderItems from "./tools/exchange_delivered_order_items.ts";
10
+ import findUserIdByEmail from "./tools/find_user_id_by_email.ts";
11
+ import findUserIdByNameZip from "./tools/find_user_id_by_name_zip.ts";
12
+ import getItemDetails from "./tools/get_item_details.ts";
13
+ import getOrderDetails from "./tools/get_order_details.ts";
14
+ import getProductDetails from "./tools/get_product_details.ts";
15
+ import getUserDetails from "./tools/get_user_details.ts";
16
+ import listAllProductTypes from "./tools/list_all_product_types.ts";
17
+ import modifyPendingOrderAddress from "./tools/modify_pending_order_address.ts";
18
+ import modifyPendingOrderItems from "./tools/modify_pending_order_items.ts";
19
+ import modifyPendingOrderPayment from "./tools/modify_pending_order_payment.ts";
20
+ import modifyUserAddress from "./tools/modify_user_address.ts";
21
+ import returnDeliveredOrderItems from "./tools/return_delivered_order_items.ts";
22
+ import transferToHumanAgents from "./tools/transfer_to_human_agents.ts";
23
+
24
+ /** Each call is its own session, so two contexts are two independent stores —
25
+ * which is what the isolation tests below rest on. */
25
26
  function makeCtx(): ToolContext {
26
- return {
27
- sessionId: `retail-test-${++sessionCounter}`,
28
- send: () => {},
29
- env: {},
30
- state: {},
31
- messages: [],
32
- } as unknown as ToolContext;
27
+ return createToolContext();
28
+ }
29
+
30
+ /**
31
+ * The success half of a tool result, or a thrown failure carrying the tool's
32
+ * own message.
33
+ *
34
+ * `ToolDef["execute"]`'s public signature always returns `unknown` — the wire
35
+ * type is fixed so any tool is assignable to `ToolDef`, whatever its body
36
+ * really returns — so every test that reads a field back off a result needed a
37
+ * cast. There were EIGHTEEN of them here, each the same three lines, which is
38
+ * the shape the repo's guides call a missing typed seam: a concentration of
39
+ * identical casts is one narrowing that belongs in one helper, not a cast per
40
+ * assertion. This is that helper, and it is the file's only cast.
41
+ *
42
+ * It also removes the second half of the boilerplate: a `ToolFailure` fails the
43
+ * test HERE, naming what the tool refused, rather than surfacing three lines
44
+ * later as `undefined` on a field nobody assigned.
45
+ */
46
+ function ok<T>(result: unknown): T {
47
+ if (isToolFailure(result)) throw new Error(`tool refused: ${result.error}`);
48
+ return result as T;
33
49
  }
34
50
 
35
51
  /** A context already authenticated as `userId`, via the real tool. */
36
52
  async function authedCtx(email: string): Promise<ToolContext> {
37
53
  const ctx = makeCtx();
38
- // `ToolDef["execute"]`'s public signature always returns `unknown` (the
39
- // wire type is fixed so any tool is assignable to `ToolDef`, regardless of
40
- // what its own `execute` body actually returns), so a cast is needed
41
- // anywhere a test reads a success field back off the result — matching the
42
- // `as {...}` convention other templates already use for the same reason.
43
- const result = (await findUserIdByEmail.execute({ email }, ctx)) as AuthResult | ErrorResult;
44
- if (isError(result)) throw new Error(`fixture failed to authenticate: ${result.error}`);
54
+ ok<AuthResult>(await findUserIdByEmail.execute({ email }, ctx));
45
55
  return ctx;
46
56
  }
47
57
 
48
58
  describe("authentication", () => {
49
59
  test("find_user_id_by_email is case-insensitive and authenticates the session", async () => {
50
60
  const ctx = makeCtx();
51
- const result = (await findUserIdByEmail.execute(
52
- { email: "OLIVIA.ITO5204@EXAMPLE.COM" },
53
- ctx,
54
- )) as AuthResult | ErrorResult;
55
- expect(isError(result) ? null : result.user_id).toBe("olivia_ito_3591");
56
- expect(getState(ctx).authenticatedUserId).toBe("olivia_ito_3591");
61
+ const result = ok<AuthResult>(
62
+ await findUserIdByEmail.execute({ email: "OLIVIA.ITO5204@EXAMPLE.COM" }, ctx),
63
+ );
64
+ expect(result.user_id).toBe("olivia_ito_3591");
65
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
57
66
  });
58
67
 
59
68
  test("an unknown email is refused and leaves the session unauthenticated", async () => {
60
69
  const ctx = makeCtx();
61
70
  const result = await findUserIdByEmail.execute({ email: "nobody@example.com" }, ctx);
62
- expect(isError(result)).toBe(true);
63
- expect(getState(ctx).authenticatedUserId).toBeNull();
71
+ expect(isToolFailure(result)).toBe(true);
72
+ expect(retailSlot.get(ctx).authenticatedUserId).toBeNull();
64
73
  });
65
74
 
66
75
  test("find_user_id_by_name_zip is case-insensitive on names and exact on zip", async () => {
67
76
  const ctx = makeCtx();
68
- const ok = (await findUserIdByNameZip.execute(
69
- { first_name: "aarav", last_name: "ANDERSON", zip: "19031" },
70
- ctx,
71
- )) as AuthResult | ErrorResult;
72
- expect(isError(ok) ? null : ok.user_id).toBe("aarav_anderson_8794");
77
+ const found = ok<AuthResult>(
78
+ await findUserIdByNameZip.execute(
79
+ { first_name: "aarav", last_name: "ANDERSON", zip: "19031" },
80
+ ctx,
81
+ ),
82
+ );
83
+ expect(found.user_id).toBe("aarav_anderson_8794");
73
84
 
74
85
  const wrongZip = await findUserIdByNameZip.execute(
75
86
  { first_name: "Aarav", last_name: "Anderson", zip: "78268" },
76
87
  makeCtx(),
77
88
  );
78
- expect(isError(wrongZip)).toBe(true);
89
+ expect(isToolFailure(wrongZip)).toBe(true);
79
90
  });
80
91
 
81
92
  test("two customers share a first name — the zip is what separates them", async () => {
82
- const a = (await findUserIdByNameZip.execute(
83
- { first_name: "Aarav", last_name: "Anderson", zip: "19031" },
84
- makeCtx(),
85
- )) as AuthResult | ErrorResult;
86
- const b = (await findUserIdByNameZip.execute(
87
- { first_name: "Aarav", last_name: "Gonzalez", zip: "78268" },
88
- makeCtx(),
89
- )) as AuthResult | ErrorResult;
90
- expect(isError(a) ? null : a.user_id).toBe("aarav_anderson_8794");
91
- expect(isError(b) ? null : b.user_id).toBe("aarav_gonzalez_5113");
93
+ const a = ok<AuthResult>(
94
+ await findUserIdByNameZip.execute(
95
+ { first_name: "Aarav", last_name: "Anderson", zip: "19031" },
96
+ makeCtx(),
97
+ ),
98
+ );
99
+ const b = ok<AuthResult>(
100
+ await findUserIdByNameZip.execute(
101
+ { first_name: "Aarav", last_name: "Gonzalez", zip: "78268" },
102
+ makeCtx(),
103
+ ),
104
+ );
105
+ expect(a.user_id).toBe("aarav_anderson_8794");
106
+ expect(b.user_id).toBe("aarav_gonzalez_5113");
92
107
  });
93
108
 
94
109
  test("re-authenticating as the same customer is a no-op success", async () => {
95
110
  const ctx = await authedCtx("olivia.ito5204@example.com");
96
111
  const again = await findUserIdByEmail.execute({ email: "olivia.ito5204@example.com" }, ctx);
97
- expect(isError(again)).toBe(false);
98
- expect(getState(ctx).authenticatedUserId).toBe("olivia_ito_3591");
112
+ expect(isToolFailure(again)).toBe(false);
113
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
99
114
  });
100
115
 
101
116
  test("switching to a DIFFERENT customer mid-conversation is refused", async () => {
@@ -104,8 +119,8 @@ describe("authentication", () => {
104
119
  { email: "aarav.anderson9752@example.com" },
105
120
  ctx,
106
121
  );
107
- expect(isError(switched) && switched.error.toLowerCase()).toContain("one customer");
108
- expect(getState(ctx).authenticatedUserId).toBe("olivia_ito_3591");
122
+ expect(isToolFailure(switched) && switched.error.toLowerCase()).toContain("one customer");
123
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
109
124
  });
110
125
 
111
126
  test("switching via name + zip is refused too — both doors, one lock", async () => {
@@ -114,8 +129,8 @@ describe("authentication", () => {
114
129
  { first_name: "Aarav", last_name: "Anderson", zip: "19031" },
115
130
  ctx,
116
131
  );
117
- expect(isError(switched)).toBe(true);
118
- expect(getState(ctx).authenticatedUserId).toBe("olivia_ito_3591");
132
+ expect(isToolFailure(switched)).toBe(true);
133
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
119
134
  });
120
135
  });
121
136
 
@@ -147,10 +162,9 @@ interface ProductTypesResult {
147
162
  describe("read tools", () => {
148
163
  test("get_user_details returns the caller's profile with gift-card balances", async () => {
149
164
  const ctx = await authedCtx("olivia.ito5204@example.com");
150
- const result = (await getUserDetails.execute({ user_id: "olivia_ito_3591" }, ctx)) as
151
- | UserDetailsResult
152
- | ErrorResult;
153
- if (isError(result)) throw new Error(result.error);
165
+ const result = ok<UserDetailsResult>(
166
+ await getUserDetails.execute({ user_id: "olivia_ito_3591" }, ctx),
167
+ );
154
168
  expect(result.email).toBe("olivia.ito5204@example.com");
155
169
  expect(result.orders).toHaveLength(5);
156
170
  const card = result.payment_methods.find((m) => m.payment_method_id === "gift_card_7794233");
@@ -160,30 +174,28 @@ describe("read tools", () => {
160
174
  test("get_user_details refuses a different user id", async () => {
161
175
  const ctx = await authedCtx("olivia.ito5204@example.com");
162
176
  const result = await getUserDetails.execute({ user_id: "aarav_anderson_8794" }, ctx);
163
- expect(isError(result)).toBe(true);
177
+ expect(isToolFailure(result)).toBe(true);
164
178
  });
165
179
 
166
180
  test("get_order_details resolves shorthand and sets focus", async () => {
167
181
  const ctx = await authedCtx("olivia.ito5204@example.com");
168
- const result = (await getOrderDetails.execute({ order_id: "the delivered one" }, ctx)) as
169
- | OrderDetailsResult
170
- | ErrorResult;
171
- if (isError(result)) throw new Error(result.error);
182
+ const result = ok<OrderDetailsResult>(
183
+ await getOrderDetails.execute({ order_id: "the delivered one" }, ctx),
184
+ );
172
185
  expect(result.order_id).toBe("#W5866402");
173
- expect(getState(ctx).focus.orderId).toBe("#W5866402");
186
+ expect(retailSlot.get(ctx).focus.orderId).toBe("#W5866402");
174
187
  });
175
188
 
176
189
  test("get_order_details refuses another customer's order", async () => {
177
190
  const ctx = await authedCtx("olivia.ito5204@example.com");
178
191
  const result = await getOrderDetails.execute({ order_id: "#W4316152" }, ctx);
179
- expect(isError(result)).toBe(true);
192
+ expect(isToolFailure(result)).toBe(true);
180
193
  });
181
194
 
182
195
  test("get_product_details lists variants and needs no authentication", async () => {
183
- const result = (await getProductDetails.execute({ product_id: "9832717871" }, makeCtx())) as
184
- | ProductDetailsResult
185
- | ErrorResult;
186
- if (isError(result)) throw new Error(result.error);
196
+ const result = ok<ProductDetailsResult>(
197
+ await getProductDetails.execute({ product_id: "9832717871" }, makeCtx()),
198
+ );
187
199
  expect(result.name).toBe("Tea Kettle");
188
200
  expect(result.variants.length).toBeGreaterThan(1);
189
201
  expect(result.variants[0]).toHaveProperty("item_id");
@@ -191,23 +203,19 @@ describe("read tools", () => {
191
203
 
192
204
  test("get_product_details rejects an item id passed as a product id, and says so", async () => {
193
205
  const result = await getProductDetails.execute({ product_id: "3909406921" }, makeCtx());
194
- expect(isError(result) && result.error).toContain("item id");
206
+ expect(isToolFailure(result) && result.error).toContain("item id");
195
207
  });
196
208
 
197
209
  test("get_item_details resolves an item without knowing its product", async () => {
198
- const result = (await getItemDetails.execute({ item_id: "3909406921" }, makeCtx())) as
199
- | ItemDetailsResult
200
- | ErrorResult;
201
- if (isError(result)) throw new Error(result.error);
210
+ const result = ok<ItemDetailsResult>(
211
+ await getItemDetails.execute({ item_id: "3909406921" }, makeCtx()),
212
+ );
202
213
  expect(result.price).toBe(98.25);
203
214
  expect(result.product_name).toBe("Tea Kettle");
204
215
  });
205
216
 
206
217
  test("list_all_product_types returns all 50, sorted by name", async () => {
207
- const result = (await listAllProductTypes.execute({}, makeCtx())) as
208
- | ProductTypesResult
209
- | ErrorResult;
210
- if (isError(result)) throw new Error(result.error);
218
+ const result = ok<ProductTypesResult>(await listAllProductTypes.execute({}, makeCtx()));
211
219
  expect(Object.keys(result.products)).toHaveLength(50);
212
220
  const names = Object.keys(result.products);
213
221
  // localeCompare, not the default lexicographic sort — the tool sorts for a
@@ -229,15 +237,16 @@ interface CancelOrderResult {
229
237
  describe("cancel_pending_order", () => {
230
238
  test("cancels a pending order and credits a gift card immediately", async () => {
231
239
  const ctx = await authedCtx("aarav.anderson9752@example.com");
232
- const result = (await cancelPendingOrder.execute(
233
- { order_id: "#W9300146", reason: "ordered by mistake" },
234
- ctx,
235
- )) as CancelOrderResult | ErrorResult;
236
- if (isError(result)) throw new Error(result.error);
240
+ const result = ok<CancelOrderResult>(
241
+ await cancelPendingOrder.execute(
242
+ { order_id: "#W9300146", reason: "ordered by mistake" },
243
+ ctx,
244
+ ),
245
+ );
237
246
  expect(result.status).toBe("cancelled");
238
247
  expect(result.refund_immediate).toBe(true);
239
248
 
240
- const state = getState(ctx);
249
+ const state = retailSlot.get(ctx);
241
250
  expect(state.store.orders["#W9300146"]?.status).toBe("cancelled");
242
251
  expect(state.store.orders["#W9300146"]?.cancel_reason).toBe("ordered by mistake");
243
252
  // $17.00 card + the $153.23 order total.
@@ -248,7 +257,7 @@ describe("cancel_pending_order", () => {
248
257
  test("appends a matching refund to the payment history", async () => {
249
258
  const ctx = await authedCtx("aarav.anderson9752@example.com");
250
259
  await cancelPendingOrder.execute({ order_id: "#W9300146", reason: "no longer needed" }, ctx);
251
- const history = getState(ctx).store.orders["#W9300146"]?.payment_history ?? [];
260
+ const history = retailSlot.get(ctx).store.orders["#W9300146"]?.payment_history ?? [];
252
261
  expect(history).toHaveLength(2);
253
262
  expect(history[1]).toMatchObject({
254
263
  transaction_type: "refund",
@@ -259,14 +268,12 @@ describe("cancel_pending_order", () => {
259
268
 
260
269
  test("a non-gift-card refund takes 5-7 days and moves no balance", async () => {
261
270
  const ctx = await authedCtx("olivia.ito5204@example.com");
262
- const result = (await cancelPendingOrder.execute(
263
- { order_id: "#W5442520", reason: "no longer needed" },
264
- ctx,
265
- )) as CancelOrderResult | ErrorResult;
266
- if (isError(result)) throw new Error(result.error);
271
+ const result = ok<CancelOrderResult>(
272
+ await cancelPendingOrder.execute({ order_id: "#W5442520", reason: "no longer needed" }, ctx),
273
+ );
267
274
  expect(result.refund_immediate).toBe(false);
268
275
  expect(result.message).toContain("5 to 7");
269
- const card = getState(ctx).store.users.olivia_ito_3591?.payment_methods.gift_card_7794233;
276
+ const card = retailSlot.get(ctx).store.users.olivia_ito_3591?.payment_methods.gift_card_7794233;
270
277
  expect(card?.source === "gift_card" && card.balance).toBe(56);
271
278
  });
272
279
 
@@ -276,8 +283,8 @@ describe("cancel_pending_order", () => {
276
283
  { order_id: "#W5353646", reason: "no longer needed" },
277
284
  ctx,
278
285
  );
279
- expect(isError(result) && result.error).toContain("processed");
280
- expect(getState(ctx).store.orders["#W5353646"]?.status).toBe("processed");
286
+ expect(isToolFailure(result) && result.error).toContain("processed");
287
+ expect(retailSlot.get(ctx).store.orders["#W5353646"]?.status).toBe("processed");
281
288
  });
282
289
 
283
290
  test("refuses a delivered order", async () => {
@@ -286,7 +293,7 @@ describe("cancel_pending_order", () => {
286
293
  { order_id: "#W5866402", reason: "no longer needed" },
287
294
  ctx,
288
295
  );
289
- expect(isError(result)).toBe(true);
296
+ expect(isToolFailure(result)).toBe(true);
290
297
  });
291
298
 
292
299
  test("refuses a reason outside tau2's two accepted values", async () => {
@@ -298,8 +305,8 @@ describe("cancel_pending_order", () => {
298
305
  { order_id: "#W9300146", reason: "changed my mind" as unknown as "no longer needed" },
299
306
  ctx,
300
307
  );
301
- expect(isError(result) && result.error).toContain("no longer needed");
302
- expect(getState(ctx).store.orders["#W9300146"]?.status).toBe("pending");
308
+ expect(isToolFailure(result) && result.error).toContain("no longer needed");
309
+ expect(retailSlot.get(ctx).store.orders["#W9300146"]?.status).toBe("pending");
303
310
  });
304
311
 
305
312
  test("refuses cancelling twice", async () => {
@@ -309,18 +316,21 @@ describe("cancel_pending_order", () => {
309
316
  { order_id: "#W9300146", reason: "no longer needed" },
310
317
  ctx,
311
318
  );
312
- expect(isError(second)).toBe(true);
313
- const card = getState(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
319
+ expect(isToolFailure(second)).toBe(true);
320
+ const card =
321
+ retailSlot.get(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
314
322
  expect(card?.source === "gift_card" && card.balance).toBe(170.23);
315
323
  });
316
324
 
317
325
  test("resolves spoken shorthand to the single pending order", async () => {
318
326
  const ctx = await authedCtx("aarav.anderson9752@example.com");
319
- const result = (await cancelPendingOrder.execute(
320
- { order_id: "my pending order", reason: "ordered by mistake" },
321
- ctx,
322
- )) as CancelOrderResult | ErrorResult;
323
- expect(isError(result) ? null : result.order_id).toBe("#W9300146");
327
+ const result = ok<CancelOrderResult>(
328
+ await cancelPendingOrder.execute(
329
+ { order_id: "my pending order", reason: "ordered by mistake" },
330
+ ctx,
331
+ ),
332
+ );
333
+ expect(result.order_id).toBe("#W9300146");
324
334
  });
325
335
 
326
336
  test("refuses ambiguous shorthand rather than cancelling the wrong order", async () => {
@@ -329,9 +339,9 @@ describe("cancel_pending_order", () => {
329
339
  { order_id: "my pending order", reason: "no longer needed" },
330
340
  ctx,
331
341
  );
332
- expect(isError(result)).toBe(true);
342
+ expect(isToolFailure(result)).toBe(true);
333
343
  const statuses = ["#W5442520", "#W7941031", "#W3657213"].map(
334
- (id) => getState(ctx).store.orders[id]?.status,
344
+ (id) => retailSlot.get(ctx).store.orders[id]?.status,
335
345
  );
336
346
  expect(statuses).toEqual(["pending", "pending", "pending"]);
337
347
  });
@@ -354,31 +364,31 @@ interface AddressToolResult {
354
364
  describe("modify_pending_order_address", () => {
355
365
  test("rewrites a pending order's shipping address", async () => {
356
366
  const ctx = await authedCtx("aarav.anderson9752@example.com");
357
- const result = (await modifyPendingOrderAddress.execute(
358
- { order_id: "#W9300146", ...NEW_ADDRESS },
359
- ctx,
360
- )) as AddressToolResult | ErrorResult;
361
- if (isError(result)) throw new Error(result.error);
367
+ const result = ok<AddressToolResult>(
368
+ await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx),
369
+ );
362
370
  expect(result.address).toEqual(NEW_ADDRESS);
363
- expect(getState(ctx).store.orders["#W9300146"]?.address).toEqual(NEW_ADDRESS);
371
+ expect(retailSlot.get(ctx).store.orders["#W9300146"]?.address).toEqual(NEW_ADDRESS);
364
372
  });
365
373
 
366
374
  test("leaves the customer's default address untouched", async () => {
367
375
  const ctx = await authedCtx("aarav.anderson9752@example.com");
368
376
  await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx);
369
- expect(getState(ctx).store.users.aarav_anderson_8794?.address.zip).toBe("19031");
377
+ expect(retailSlot.get(ctx).store.users.aarav_anderson_8794?.address.zip).toBe("19031");
370
378
  });
371
379
 
372
380
  test("accepts a 'pending (item modified)' order — unlike cancel", async () => {
373
381
  const ctx = await authedCtx("aarav.anderson9752@example.com");
374
- const order = getState(ctx).store.orders["#W9300146"];
375
- if (!order) throw new Error("fixture missing");
376
- order.status = "pending (item modified)";
382
+ retailSlot.update(ctx, (state) => {
383
+ const order = state.store.orders["#W9300146"];
384
+ if (!order) throw new Error("fixture missing");
385
+ order.status = "pending (item modified)";
386
+ });
377
387
  const result = await modifyPendingOrderAddress.execute(
378
388
  { order_id: "#W9300146", ...NEW_ADDRESS },
379
389
  ctx,
380
390
  );
381
- expect(isError(result)).toBe(false);
391
+ expect(isToolFailure(result)).toBe(false);
382
392
  });
383
393
 
384
394
  test("refuses a delivered order", async () => {
@@ -387,7 +397,7 @@ describe("modify_pending_order_address", () => {
387
397
  { order_id: "#W5866402", ...NEW_ADDRESS },
388
398
  ctx,
389
399
  );
390
- expect(isError(result)).toBe(true);
400
+ expect(isToolFailure(result)).toBe(true);
391
401
  });
392
402
 
393
403
  test("refuses another customer's order", async () => {
@@ -396,26 +406,22 @@ describe("modify_pending_order_address", () => {
396
406
  { order_id: "#W9300146", ...NEW_ADDRESS },
397
407
  ctx,
398
408
  );
399
- expect(isError(result)).toBe(true);
409
+ expect(isToolFailure(result)).toBe(true);
400
410
  });
401
411
  });
402
412
 
403
413
  describe("modify_user_address", () => {
404
414
  test("rewrites the customer's default address", async () => {
405
415
  const ctx = await authedCtx("emma.smith3991@example.com");
406
- const result = await modifyUserAddress.execute(
407
- { user_id: "emma_smith_8564", ...NEW_ADDRESS },
408
- ctx,
409
- );
410
- if (isError(result)) throw new Error(result.error);
411
- expect(getState(ctx).store.users.emma_smith_8564?.address).toEqual(NEW_ADDRESS);
416
+ ok(await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx));
417
+ expect(retailSlot.get(ctx).store.users.emma_smith_8564?.address).toEqual(NEW_ADDRESS);
412
418
  });
413
419
 
414
420
  test("leaves existing orders' addresses untouched", async () => {
415
421
  const ctx = await authedCtx("emma.smith3991@example.com");
416
- const before = structuredClone(getState(ctx).store.orders["#W2417020"]?.address);
422
+ const before = structuredClone(retailSlot.get(ctx).store.orders["#W2417020"]?.address);
417
423
  await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx);
418
- expect(getState(ctx).store.orders["#W2417020"]?.address).toEqual(before);
424
+ expect(retailSlot.get(ctx).store.orders["#W2417020"]?.address).toEqual(before);
419
425
  });
420
426
 
421
427
  test("refuses a different user id", async () => {
@@ -424,8 +430,8 @@ describe("modify_user_address", () => {
424
430
  { user_id: "olivia_ito_3591", ...NEW_ADDRESS },
425
431
  ctx,
426
432
  );
427
- expect(isError(result)).toBe(true);
428
- expect(getState(ctx).store.users.olivia_ito_3591?.address.zip).toBe("80218");
433
+ expect(isToolFailure(result)).toBe(true);
434
+ expect(retailSlot.get(ctx).store.users.olivia_ito_3591?.address.zip).toBe("80218");
429
435
  });
430
436
 
431
437
  test("requires authentication", async () => {
@@ -433,7 +439,7 @@ describe("modify_user_address", () => {
433
439
  { user_id: "emma_smith_8564", ...NEW_ADDRESS },
434
440
  makeCtx(),
435
441
  );
436
- expect(isError(result) && result.error).toContain("find_user_id_by_email");
442
+ expect(isToolFailure(result) && result.error).toContain("find_user_id_by_email");
437
443
  });
438
444
  });
439
445
 
@@ -448,20 +454,21 @@ interface ModifyItemsResult {
448
454
  describe("modify_pending_order_items", () => {
449
455
  test("swaps an item, charges the difference to a gift card, and goes terminal", async () => {
450
456
  const ctx = await authedCtx("emma.smith3991@example.com");
451
- const result = (await modifyPendingOrderItems.execute(
452
- {
453
- order_id: "#W2417020",
454
- item_ids: ["8997785118"],
455
- new_item_ids: ["6017636844"],
456
- payment_method_id: "gift_card_8541487",
457
- },
458
- ctx,
459
- )) as ModifyItemsResult | ErrorResult;
460
- if (isError(result)) throw new Error(result.error);
457
+ const result = ok<ModifyItemsResult>(
458
+ await modifyPendingOrderItems.execute(
459
+ {
460
+ order_id: "#W2417020",
461
+ item_ids: ["8997785118"],
462
+ new_item_ids: ["6017636844"],
463
+ payment_method_id: "gift_card_8541487",
464
+ },
465
+ ctx,
466
+ ),
467
+ );
461
468
  expect(result.price_difference).toBe(-382.03);
462
469
  expect(result.status).toBe("pending (item modified)");
463
470
 
464
- const state = getState(ctx);
471
+ const state = retailSlot.get(ctx);
465
472
  const order = state.store.orders["#W2417020"];
466
473
  expect(order?.items[0]?.item_id).toBe("6017636844");
467
474
  expect(order?.items[0]?.price).toBe(2292.37);
@@ -481,7 +488,7 @@ describe("modify_pending_order_items", () => {
481
488
  },
482
489
  ctx,
483
490
  );
484
- const history = getState(ctx).store.orders["#W2417020"]?.payment_history ?? [];
491
+ const history = retailSlot.get(ctx).store.orders["#W2417020"]?.payment_history ?? [];
485
492
  expect(history[1]).toMatchObject({
486
493
  transaction_type: "refund",
487
494
  amount: 382.03,
@@ -509,7 +516,7 @@ describe("modify_pending_order_items", () => {
509
516
  },
510
517
  ctx,
511
518
  );
512
- expect(isError(second) && second.error).toContain("pending (item modified)");
519
+ expect(isToolFailure(second) && second.error).toContain("pending (item modified)");
513
520
  });
514
521
 
515
522
  test("a modified order can no longer be cancelled", async () => {
@@ -527,7 +534,7 @@ describe("modify_pending_order_items", () => {
527
534
  { order_id: "#W2417020", reason: "no longer needed" },
528
535
  ctx,
529
536
  );
530
- expect(isError(cancelled)).toBe(true);
537
+ expect(isToolFailure(cancelled)).toBe(true);
531
538
  });
532
539
 
533
540
  test("refuses when the gift card cannot cover the difference, changing nothing", async () => {
@@ -545,9 +552,9 @@ describe("modify_pending_order_items", () => {
545
552
  },
546
553
  ctx,
547
554
  );
548
- expect(isError(result) && result.error.toLowerCase()).toContain("balance");
555
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("balance");
549
556
 
550
- const state = getState(ctx);
557
+ const state = retailSlot.get(ctx);
551
558
  const order = state.store.orders["#W6436609"];
552
559
  expect(order?.status).toBe("pending");
553
560
  expect(order?.items.map((i) => i.item_id)).toContain("6017636844");
@@ -558,18 +565,19 @@ describe("modify_pending_order_items", () => {
558
565
 
559
566
  test("the same upgrade succeeds on a payment method with no balance to run out of", async () => {
560
567
  const ctx = await authedCtx("anya.garcia2061@example.com");
561
- const result = (await modifyPendingOrderItems.execute(
562
- {
563
- order_id: "#W6436609",
564
- item_ids: ["6017636844"],
565
- new_item_ids: ["9844888101"],
566
- payment_method_id: "credit_card_8955149",
567
- },
568
- ctx,
569
- )) as ModifyItemsResult | ErrorResult;
570
- if (isError(result)) throw new Error(result.error);
568
+ const result = ok<ModifyItemsResult>(
569
+ await modifyPendingOrderItems.execute(
570
+ {
571
+ order_id: "#W6436609",
572
+ item_ids: ["6017636844"],
573
+ new_item_ids: ["9844888101"],
574
+ payment_method_id: "credit_card_8955149",
575
+ },
576
+ ctx,
577
+ ),
578
+ );
571
579
  expect(result.price_difference).toBe(167.37);
572
- const history = getState(ctx).store.orders["#W6436609"]?.payment_history ?? [];
580
+ const history = retailSlot.get(ctx).store.orders["#W6436609"]?.payment_history ?? [];
573
581
  expect(history[1]).toMatchObject({
574
582
  transaction_type: "payment",
575
583
  amount: 167.37,
@@ -588,7 +596,7 @@ describe("modify_pending_order_items", () => {
588
596
  },
589
597
  ctx,
590
598
  );
591
- expect(isError(result)).toBe(true);
599
+ expect(isToolFailure(result)).toBe(true);
592
600
  });
593
601
 
594
602
  test("refuses a payment method belonging to another customer", async () => {
@@ -602,8 +610,8 @@ describe("modify_pending_order_items", () => {
602
610
  },
603
611
  ctx,
604
612
  );
605
- expect(isError(result)).toBe(true);
606
- expect(getState(ctx).store.orders["#W2417020"]?.status).toBe("pending");
613
+ expect(isToolFailure(result)).toBe(true);
614
+ expect(retailSlot.get(ctx).store.orders["#W2417020"]?.status).toBe("pending");
607
615
  });
608
616
  });
609
617
 
@@ -620,14 +628,15 @@ describe("modify_pending_order_payment", () => {
620
628
  test("moves an order to a different method, refunding the old one", async () => {
621
629
  const ctx = await authedCtx("olivia.ito5204@example.com");
622
630
  // #W5442520 is $663.85 on credit_card_9753331. Pay by PayPal instead.
623
- const result = (await modifyPendingOrderPayment.execute(
624
- { order_id: "#W5442520", payment_method_id: "paypal_8049766" },
625
- ctx,
626
- )) as ModifyPaymentResult | ErrorResult;
627
- if (isError(result)) throw new Error(result.error);
631
+ const result = ok<ModifyPaymentResult>(
632
+ await modifyPendingOrderPayment.execute(
633
+ { order_id: "#W5442520", payment_method_id: "paypal_8049766" },
634
+ ctx,
635
+ ),
636
+ );
628
637
  expect(result.status).toBe("pending");
629
638
 
630
- const history = getState(ctx).store.orders["#W5442520"]?.payment_history ?? [];
639
+ const history = retailSlot.get(ctx).store.orders["#W5442520"]?.payment_history ?? [];
631
640
  expect(history).toHaveLength(3);
632
641
  expect(history[1]).toMatchObject({
633
642
  transaction_type: "payment",
@@ -648,8 +657,9 @@ describe("modify_pending_order_payment", () => {
648
657
  { order_id: "#W9160732", payment_method_id: "paypal_6121064" },
649
658
  ctx,
650
659
  );
651
- expect(isError(result)).toBe(false);
652
- const card = getState(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
660
+ expect(isToolFailure(result)).toBe(false);
661
+ const card =
662
+ retailSlot.get(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
653
663
  expect(card?.source === "gift_card" && card.balance).toBe(1107.54);
654
664
  });
655
665
 
@@ -660,10 +670,11 @@ describe("modify_pending_order_payment", () => {
660
670
  { order_id: "#W6979932", payment_method_id: "gift_card_5979071" },
661
671
  ctx,
662
672
  );
663
- expect(isError(result) && result.error.toLowerCase()).toContain("balance");
664
- const card = getState(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
673
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("balance");
674
+ const card =
675
+ retailSlot.get(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
665
676
  expect(card?.source === "gift_card" && card.balance).toBe(96);
666
- expect(getState(ctx).store.orders["#W6979932"]?.payment_history).toHaveLength(1);
677
+ expect(retailSlot.get(ctx).store.orders["#W6979932"]?.payment_history).toHaveLength(1);
667
678
  });
668
679
 
669
680
  test("refuses the method the order already uses", async () => {
@@ -672,25 +683,27 @@ describe("modify_pending_order_payment", () => {
672
683
  { order_id: "#W5442520", payment_method_id: "credit_card_9753331" },
673
684
  ctx,
674
685
  );
675
- expect(isError(result) && result.error.toLowerCase()).toContain("different");
686
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("different");
676
687
  });
677
688
 
678
689
  test("refuses an order whose payment history is not a single payment", async () => {
679
690
  const ctx = await authedCtx("olivia.ito5204@example.com");
680
- const order = getState(ctx).store.orders["#W5442520"];
681
- if (!order) throw new Error("fixture missing");
682
691
  // No seeded pending order has a second history entry, so construct one —
683
692
  // tau2 guards this case and the guard should still be covered.
684
- order.payment_history.push({
685
- transaction_type: "refund",
686
- amount: 10,
687
- payment_method_id: "credit_card_9753331",
693
+ retailSlot.update(ctx, (state) => {
694
+ const order = state.store.orders["#W5442520"];
695
+ if (!order) throw new Error("fixture missing");
696
+ order.payment_history.push({
697
+ transaction_type: "refund",
698
+ amount: 10,
699
+ payment_method_id: "credit_card_9753331",
700
+ });
688
701
  });
689
702
  const result = await modifyPendingOrderPayment.execute(
690
703
  { order_id: "#W5442520", payment_method_id: "paypal_8049766" },
691
704
  ctx,
692
705
  );
693
- expect(isError(result) && result.error.toLowerCase()).toContain("exactly one payment");
706
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("exactly one payment");
694
707
  });
695
708
 
696
709
  test("refuses a delivered order", async () => {
@@ -699,7 +712,7 @@ describe("modify_pending_order_payment", () => {
699
712
  { order_id: "#W5866402", payment_method_id: "gift_card_7794233" },
700
713
  ctx,
701
714
  );
702
- expect(isError(result)).toBe(true);
715
+ expect(isToolFailure(result)).toBe(true);
703
716
  });
704
717
 
705
718
  test("refuses a method not on the customer's profile", async () => {
@@ -708,7 +721,7 @@ describe("modify_pending_order_payment", () => {
708
721
  { order_id: "#W5442520", payment_method_id: "gift_card_7245904" },
709
722
  ctx,
710
723
  );
711
- expect(isError(result)).toBe(true);
724
+ expect(isToolFailure(result)).toBe(true);
712
725
  });
713
726
  });
714
727
 
@@ -724,25 +737,25 @@ interface ExchangeResult {
724
737
  order_id: string;
725
738
  status: string;
726
739
  price_difference: number;
727
- exchange_items: string[];
728
- exchange_new_items: string[];
740
+ exchanges: { item_id: string; new_item_id: string; price_difference: number }[];
729
741
  message: string;
730
742
  }
731
743
 
732
744
  describe("return_delivered_order_items", () => {
733
745
  test("requests a return refunded to the original method", async () => {
734
746
  const ctx = await authedCtx("harper.brown3965@example.com");
735
- const result = (await returnDeliveredOrderItems.execute(
736
- {
737
- order_id: "#W1840144",
738
- item_ids: ["8590708195"],
739
- payment_method_id: "paypal_2306935",
740
- },
741
- ctx,
742
- )) as ReturnResult | ErrorResult;
743
- if (isError(result)) throw new Error(result.error);
747
+ const result = ok<ReturnResult>(
748
+ await returnDeliveredOrderItems.execute(
749
+ {
750
+ order_id: "#W1840144",
751
+ item_ids: ["8590708195"],
752
+ payment_method_id: "paypal_2306935",
753
+ },
754
+ ctx,
755
+ ),
756
+ );
744
757
  expect(result.status).toBe("return requested");
745
- const order = getState(ctx).store.orders["#W1840144"];
758
+ const order = retailSlot.get(ctx).store.orders["#W1840144"];
746
759
  expect(order?.return_items).toEqual(["8590708195"]);
747
760
  expect(order?.return_payment_method_id).toBe("paypal_2306935");
748
761
  });
@@ -758,7 +771,7 @@ describe("return_delivered_order_items", () => {
758
771
  },
759
772
  ctx,
760
773
  );
761
- expect(isError(result)).toBe(false);
774
+ expect(isToolFailure(result)).toBe(false);
762
775
  });
763
776
 
764
777
  test("refuses a non-original, non-gift-card method", async () => {
@@ -772,8 +785,8 @@ describe("return_delivered_order_items", () => {
772
785
  },
773
786
  ctx,
774
787
  );
775
- expect(isError(result) && result.error.toLowerCase()).toContain("original");
776
- expect(getState(ctx).store.orders["#W1840144"]?.status).toBe("delivered");
788
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("original");
789
+ expect(retailSlot.get(ctx).store.orders["#W1840144"]?.status).toBe("delivered");
777
790
  });
778
791
 
779
792
  test("returns both copies of a duplicate item and sorts the recorded list", async () => {
@@ -786,8 +799,8 @@ describe("return_delivered_order_items", () => {
786
799
  },
787
800
  ctx,
788
801
  );
789
- expect(isError(result)).toBe(false);
790
- expect(getState(ctx).store.orders["#W1840144"]?.return_items).toEqual([
802
+ expect(isToolFailure(result)).toBe(false);
803
+ expect(retailSlot.get(ctx).store.orders["#W1840144"]?.return_items).toEqual([
791
804
  "6534134392",
792
805
  "8590708195",
793
806
  "8590708195",
@@ -804,7 +817,7 @@ describe("return_delivered_order_items", () => {
804
817
  },
805
818
  ctx,
806
819
  );
807
- expect(isError(result)).toBe(true);
820
+ expect(isToolFailure(result)).toBe(true);
808
821
  });
809
822
 
810
823
  test("refuses a pending order and refuses a second return", async () => {
@@ -817,7 +830,7 @@ describe("return_delivered_order_items", () => {
817
830
  },
818
831
  ctx,
819
832
  );
820
- expect(isError(pending)).toBe(true);
833
+ expect(isToolFailure(pending)).toBe(true);
821
834
 
822
835
  await returnDeliveredOrderItems.execute(
823
836
  { order_id: "#W1840144", item_ids: ["8590708195"], payment_method_id: "paypal_2306935" },
@@ -827,31 +840,74 @@ describe("return_delivered_order_items", () => {
827
840
  { order_id: "#W1840144", item_ids: ["6534134392"], payment_method_id: "paypal_2306935" },
828
841
  ctx,
829
842
  );
830
- expect(isError(again) && again.error).toContain("return requested");
843
+ expect(isToolFailure(again) && again.error).toContain("return requested");
831
844
  });
832
845
  });
833
846
 
834
847
  describe("exchange_delivered_order_items", () => {
835
848
  test("records an exchange with a positive price difference", async () => {
836
849
  const ctx = await authedCtx("aarav.anderson9752@example.com");
837
- const result = (await exchangeDeliveredOrderItems.execute(
838
- {
839
- order_id: "#W4316152",
840
- item_ids: ["7292993796"],
841
- new_item_ids: ["3909406921"],
842
- payment_method_id: "gift_card_7245904",
843
- },
844
- ctx,
845
- )) as ExchangeResult | ErrorResult;
846
- if (isError(result)) throw new Error(result.error);
850
+ const result = ok<ExchangeResult>(
851
+ await exchangeDeliveredOrderItems.execute(
852
+ {
853
+ order_id: "#W4316152",
854
+ item_ids: ["7292993796"],
855
+ new_item_ids: ["3909406921"],
856
+ payment_method_id: "gift_card_7245904",
857
+ },
858
+ ctx,
859
+ ),
860
+ );
847
861
  expect(result.price_difference).toBe(3.45);
848
- const order = getState(ctx).store.orders["#W4316152"];
862
+ expect(result.exchanges).toEqual([
863
+ { item_id: "7292993796", new_item_id: "3909406921", price_difference: 3.45 },
864
+ ]);
865
+ const order = retailSlot.get(ctx).store.orders["#W4316152"];
849
866
  expect(order?.status).toBe("exchange requested");
850
867
  expect(order?.exchange_items).toEqual(["7292993796"]);
851
868
  expect(order?.exchange_new_items).toEqual(["3909406921"]);
852
869
  expect(order?.exchange_price_difference).toBe(3.45);
853
870
  });
854
871
 
872
+ test("the ANSWER pairs by position even where the stored fields sort separately", async () => {
873
+ // `#W5866402` is the case that makes the difference visible: the two
874
+ // requested replacements sort into the OTHER order, so the two stored lists
875
+ // — tau2's `sorted(item_ids)` / `sorted(new_item_ids)`, kept for fidelity
876
+ // with its expected end states — read as espresso→sneaker if anything
877
+ // treats them as a pairing. `planItemSwap` priced this positionally, so the
878
+ // result has to report the pairing that was priced, not the one the sort
879
+ // produced.
880
+ const ctx = await authedCtx("olivia.ito5204@example.com");
881
+ const result = ok<ExchangeResult>(
882
+ await exchangeDeliveredOrderItems.execute(
883
+ {
884
+ order_id: "#W5866402",
885
+ item_ids: ["6242772310", "9727387530"],
886
+ new_item_ids: ["7407838442", "2509076505"],
887
+ payment_method_id: "paypal_8049766",
888
+ },
889
+ ctx,
890
+ ),
891
+ );
892
+
893
+ expect(result.exchanges).toEqual([
894
+ { item_id: "6242772310", new_item_id: "7407838442", price_difference: 85.88 },
895
+ { item_id: "9727387530", new_item_id: "2509076505", price_difference: -18.25 },
896
+ ]);
897
+ // The per-line differences add up to the quoted total — which is what makes
898
+ // the answer internally consistent rather than two numbers side by side.
899
+ expect(result.exchanges.reduce((sum, one) => sum + one.price_difference, 0)).toBeCloseTo(
900
+ result.price_difference,
901
+ 5,
902
+ );
903
+
904
+ // The stored fields keep tau2's shape: two sorted SETS, and the second one
905
+ // really is in the other order.
906
+ const order = retailSlot.get(ctx).store.orders["#W5866402"];
907
+ expect(order?.exchange_items).toEqual(["6242772310", "9727387530"]);
908
+ expect(order?.exchange_new_items).toEqual(["2509076505", "7407838442"]);
909
+ });
910
+
855
911
  test("does NOT move the gift-card balance — it is a request, not a settlement", async () => {
856
912
  const ctx = await authedCtx("aarav.anderson9752@example.com");
857
913
  await exchangeDeliveredOrderItems.execute(
@@ -863,7 +919,8 @@ describe("exchange_delivered_order_items", () => {
863
919
  },
864
920
  ctx,
865
921
  );
866
- const card = getState(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
922
+ const card =
923
+ retailSlot.get(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
867
924
  expect(card?.source === "gift_card" && card.balance).toBe(17);
868
925
  });
869
926
 
@@ -878,7 +935,7 @@ describe("exchange_delivered_order_items", () => {
878
935
  },
879
936
  ctx,
880
937
  );
881
- expect(getState(ctx).store.orders["#W4316152"]?.items.map((i) => i.item_id)).toEqual([
938
+ expect(retailSlot.get(ctx).store.orders["#W4316152"]?.items.map((i) => i.item_id)).toEqual([
882
939
  "7292993796",
883
940
  "7292993796",
884
941
  ]);
@@ -886,30 +943,34 @@ describe("exchange_delivered_order_items", () => {
886
943
 
887
944
  test("exchanges both copies of a duplicate and doubles the difference", async () => {
888
945
  const ctx = await authedCtx("aarav.anderson9752@example.com");
889
- const result = (await exchangeDeliveredOrderItems.execute(
890
- {
891
- order_id: "#W4316152",
892
- item_ids: ["7292993796", "7292993796"],
893
- new_item_ids: ["3909406921", "3909406921"],
894
- payment_method_id: "gift_card_7245904",
895
- },
896
- ctx,
897
- )) as ExchangeResult | ErrorResult;
898
- expect(isError(result) ? null : result.price_difference).toBe(6.9);
946
+ const result = ok<ExchangeResult>(
947
+ await exchangeDeliveredOrderItems.execute(
948
+ {
949
+ order_id: "#W4316152",
950
+ item_ids: ["7292993796", "7292993796"],
951
+ new_item_ids: ["3909406921", "3909406921"],
952
+ payment_method_id: "gift_card_7245904",
953
+ },
954
+ ctx,
955
+ ),
956
+ );
957
+ expect(result.price_difference).toBe(6.9);
899
958
  });
900
959
 
901
960
  test("records a negative difference as a refund direction", async () => {
902
961
  const ctx = await authedCtx("olivia.ito5204@example.com");
903
- const result = (await exchangeDeliveredOrderItems.execute(
904
- {
905
- order_id: "#W5866402",
906
- item_ids: ["6242772310"],
907
- new_item_ids: ["6200867091"],
908
- payment_method_id: "gift_card_7794233",
909
- },
910
- ctx,
911
- )) as ExchangeResult | ErrorResult;
912
- expect(isError(result) ? null : result.price_difference).toBe(-40.86);
962
+ const result = ok<ExchangeResult>(
963
+ await exchangeDeliveredOrderItems.execute(
964
+ {
965
+ order_id: "#W5866402",
966
+ item_ids: ["6242772310"],
967
+ new_item_ids: ["6200867091"],
968
+ payment_method_id: "gift_card_7794233",
969
+ },
970
+ ctx,
971
+ ),
972
+ );
973
+ expect(result.price_difference).toBe(-40.86);
913
974
  });
914
975
 
915
976
  test("refuses a gift card that cannot cover the difference", async () => {
@@ -923,8 +984,8 @@ describe("exchange_delivered_order_items", () => {
923
984
  },
924
985
  ctx,
925
986
  );
926
- expect(isError(result) && result.error.toLowerCase()).toContain("balance");
927
- expect(getState(ctx).store.orders["#W9311069"]?.status).toBe("delivered");
987
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("balance");
988
+ expect(retailSlot.get(ctx).store.orders["#W9311069"]?.status).toBe("delivered");
928
989
  });
929
990
 
930
991
  test("refuses a cross-product swap", async () => {
@@ -938,7 +999,7 @@ describe("exchange_delivered_order_items", () => {
938
999
  },
939
1000
  ctx,
940
1001
  );
941
- expect(isError(result)).toBe(true);
1002
+ expect(isToolFailure(result)).toBe(true);
942
1003
  });
943
1004
 
944
1005
  test("refuses an unavailable target", async () => {
@@ -952,7 +1013,7 @@ describe("exchange_delivered_order_items", () => {
952
1013
  },
953
1014
  ctx,
954
1015
  );
955
- expect(isError(result) && result.error.toLowerCase()).toContain("not available");
1016
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("not available");
956
1017
  });
957
1018
 
958
1019
  test("refuses a pending order and refuses a second exchange", async () => {
@@ -966,7 +1027,7 @@ describe("exchange_delivered_order_items", () => {
966
1027
  },
967
1028
  ctx,
968
1029
  );
969
- expect(isError(pending)).toBe(true);
1030
+ expect(isToolFailure(pending)).toBe(true);
970
1031
 
971
1032
  await exchangeDeliveredOrderItems.execute(
972
1033
  {
@@ -986,7 +1047,7 @@ describe("exchange_delivered_order_items", () => {
986
1047
  },
987
1048
  ctx,
988
1049
  );
989
- expect(isError(again)).toBe(true);
1050
+ expect(isToolFailure(again)).toBe(true);
990
1051
  });
991
1052
 
992
1053
  test("a returned order can no longer be exchanged", async () => {
@@ -1008,7 +1069,7 @@ describe("exchange_delivered_order_items", () => {
1008
1069
  },
1009
1070
  ctx,
1010
1071
  );
1011
- expect(isError(result)).toBe(true);
1072
+ expect(isToolFailure(result)).toBe(true);
1012
1073
  });
1013
1074
  });
1014
1075
 
@@ -1020,11 +1081,13 @@ interface TransferResult {
1020
1081
 
1021
1082
  describe("transfer_to_human_agents", () => {
1022
1083
  test("works without authentication — the escape hatch cannot be gated", async () => {
1023
- const result = (await transferToHumanAgents.execute(
1024
- { summary: "Caller wants to dispute a charge from 2019." },
1025
- makeCtx(),
1026
- )) as TransferResult | ErrorResult;
1027
- expect(isError(result)).toBe(false);
1028
- expect(isError(result) ? null : result.transferred).toBe(true);
1084
+ const result = ok<TransferResult>(
1085
+ await transferToHumanAgents.execute(
1086
+ { summary: "Caller wants to dispute a charge from 2019." },
1087
+ makeCtx(),
1088
+ ),
1089
+ );
1090
+ expect(isToolFailure(result)).toBe(false);
1091
+ expect(result.transferred).toBe(true);
1029
1092
  });
1030
1093
  });