@alexkroman1/aai-cli 5.13.2 → 5.14.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 (64) hide show
  1. package/dist/scaffold/package.json +3 -3
  2. package/dist/scaffold/pnpm-workspace.yaml +24 -0
  3. package/dist/templates/dispatch-center/agent.test.ts +10 -17
  4. package/dist/templates/dispatch-center/agent.ts +2 -2
  5. package/dist/templates/dispatch-center/client.tsx +9 -13
  6. package/dist/templates/dispatch-center/shared.ts +36 -63
  7. package/dist/templates/dispatch-center/tools/incident_add_note.ts +4 -4
  8. package/dist/templates/dispatch-center/tools/incident_create.ts +2 -2
  9. package/dist/templates/dispatch-center/tools/incident_escalate.ts +4 -4
  10. package/dist/templates/dispatch-center/tools/incident_get.ts +9 -4
  11. package/dist/templates/dispatch-center/tools/incident_triage.ts +4 -4
  12. package/dist/templates/dispatch-center/tools/incident_update_status.ts +4 -4
  13. package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -2
  14. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +2 -2
  15. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +4 -4
  16. package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -2
  17. package/dist/templates/dispatch-center/tools/resources_update_status.ts +2 -2
  18. package/dist/templates/infocom-adventure/agent.ts +10 -10
  19. package/dist/templates/infocom-adventure/client.tsx +5 -17
  20. package/dist/templates/infocom-adventure/shared.ts +17 -14
  21. package/dist/templates/pizza-ordering/agent.test.ts +27 -31
  22. package/dist/templates/pizza-ordering/agent.ts +8 -8
  23. package/dist/templates/pizza-ordering/client.tsx +4 -4
  24. package/dist/templates/pizza-ordering/shared.ts +14 -20
  25. package/dist/templates/retail/agent.test.ts +137 -138
  26. package/dist/templates/retail/agent.ts +7 -10
  27. package/dist/templates/retail/authenticate.ts +2 -2
  28. package/dist/templates/retail/client.tsx +13 -14
  29. package/dist/templates/retail/registry.test.ts +9 -9
  30. package/dist/templates/retail/resolve.test.ts +17 -16
  31. package/dist/templates/retail/resolve.ts +5 -4
  32. package/dist/templates/retail/shared.test.ts +17 -16
  33. package/dist/templates/retail/shared.ts +39 -18
  34. package/dist/templates/retail/store.test.ts +40 -47
  35. package/dist/templates/retail/store.ts +39 -65
  36. package/dist/templates/retail/swap.test.ts +20 -17
  37. package/dist/templates/retail/swap.ts +7 -14
  38. package/dist/templates/retail/tools/cancel_pending_order.ts +5 -4
  39. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +6 -5
  40. package/dist/templates/retail/tools/find_user_id_by_email.ts +2 -2
  41. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +2 -2
  42. package/dist/templates/retail/tools/get_item_details.ts +4 -3
  43. package/dist/templates/retail/tools/get_order_details.ts +4 -3
  44. package/dist/templates/retail/tools/get_product_details.ts +4 -3
  45. package/dist/templates/retail/tools/get_user_details.ts +4 -3
  46. package/dist/templates/retail/tools/list_all_product_types.ts +2 -2
  47. package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -3
  48. package/dist/templates/retail/tools/modify_pending_order_items.ts +6 -6
  49. package/dist/templates/retail/tools/modify_pending_order_payment.ts +6 -6
  50. package/dist/templates/retail/tools/modify_user_address.ts +4 -3
  51. package/dist/templates/retail/tools/return_delivered_order_items.ts +6 -6
  52. package/dist/templates/solo-rpg/agent.test.ts +26 -34
  53. package/dist/templates/solo-rpg/agent.ts +2 -2
  54. package/dist/templates/solo-rpg/client.tsx +1 -1
  55. package/dist/templates/solo-rpg/shared.ts +6 -14
  56. package/dist/templates/solo-rpg/tools/action_roll.ts +2 -2
  57. package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -2
  58. package/dist/templates/solo-rpg/tools/check_state.ts +2 -2
  59. package/dist/templates/solo-rpg/tools/load_game.ts +2 -2
  60. package/dist/templates/solo-rpg/tools/oracle.ts +3 -3
  61. package/dist/templates/solo-rpg/tools/save_game.ts +2 -2
  62. package/dist/templates/solo-rpg/tools/setup_character.ts +2 -2
  63. package/dist/templates/solo-rpg/tools/update_state.ts +12 -11
  64. package/package.json +3 -3
@@ -1,9 +1,10 @@
1
- import type { ToolContext } from "@alexkroman1/aai";
1
+ import type { ToolContext, ToolFailure } 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 { retailSlot, type StateSlot } from "./store.ts";
7
8
  import { cancelPendingOrder } from "./tools/cancel_pending_order.ts";
8
9
  import { exchangeDeliveredOrderItems } from "./tools/exchange_delivered_order_items.ts";
9
10
  import { findUserIdByEmail } from "./tools/find_user_id_by_email.ts";
@@ -20,28 +21,22 @@ import { modifyUserAddress } from "./tools/modify_user_address.ts";
20
21
  import { returnDeliveredOrderItems } from "./tools/return_delivered_order_items.ts";
21
22
  import { transferToHumanAgents } from "./tools/transfer_to_human_agents.ts";
22
23
 
23
- let sessionCounter = 0;
24
-
25
- function makeCtx(): ToolContext {
26
- return {
27
- sessionId: `retail-test-${++sessionCounter}`,
28
- send: () => {},
29
- env: {},
30
- state: {},
31
- messages: [],
32
- } as unknown as ToolContext;
24
+ /** Each call is its own session, so two contexts are two independent stores —
25
+ * which is what the isolation tests below rest on. */
26
+ function makeCtx(): ToolContext<StateSlot> {
27
+ return createToolContext<StateSlot>();
33
28
  }
34
29
 
35
30
  /** A context already authenticated as `userId`, via the real tool. */
36
- async function authedCtx(email: string): Promise<ToolContext> {
31
+ async function authedCtx(email: string): Promise<ToolContext<StateSlot>> {
37
32
  const ctx = makeCtx();
38
33
  // `ToolDef["execute"]`'s public signature always returns `unknown` (the
39
34
  // wire type is fixed so any tool is assignable to `ToolDef`, regardless of
40
35
  // what its own `execute` body actually returns), so a cast is needed
41
36
  // anywhere a test reads a success field back off the result — matching the
42
37
  // `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}`);
38
+ const result = (await findUserIdByEmail.execute({ email }, ctx)) as AuthResult | ToolFailure;
39
+ if (isToolFailure(result)) throw new Error(`fixture failed to authenticate: ${result.error}`);
45
40
  return ctx;
46
41
  }
47
42
 
@@ -51,16 +46,16 @@ describe("authentication", () => {
51
46
  const result = (await findUserIdByEmail.execute(
52
47
  { email: "OLIVIA.ITO5204@EXAMPLE.COM" },
53
48
  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");
49
+ )) as AuthResult | ToolFailure;
50
+ expect(isToolFailure(result) ? null : result.user_id).toBe("olivia_ito_3591");
51
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
57
52
  });
58
53
 
59
54
  test("an unknown email is refused and leaves the session unauthenticated", async () => {
60
55
  const ctx = makeCtx();
61
56
  const result = await findUserIdByEmail.execute({ email: "nobody@example.com" }, ctx);
62
- expect(isError(result)).toBe(true);
63
- expect(getState(ctx).authenticatedUserId).toBeNull();
57
+ expect(isToolFailure(result)).toBe(true);
58
+ expect(retailSlot.get(ctx).authenticatedUserId).toBeNull();
64
59
  });
65
60
 
66
61
  test("find_user_id_by_name_zip is case-insensitive on names and exact on zip", async () => {
@@ -68,34 +63,34 @@ describe("authentication", () => {
68
63
  const ok = (await findUserIdByNameZip.execute(
69
64
  { first_name: "aarav", last_name: "ANDERSON", zip: "19031" },
70
65
  ctx,
71
- )) as AuthResult | ErrorResult;
72
- expect(isError(ok) ? null : ok.user_id).toBe("aarav_anderson_8794");
66
+ )) as AuthResult | ToolFailure;
67
+ expect(isToolFailure(ok) ? null : ok.user_id).toBe("aarav_anderson_8794");
73
68
 
74
69
  const wrongZip = await findUserIdByNameZip.execute(
75
70
  { first_name: "Aarav", last_name: "Anderson", zip: "78268" },
76
71
  makeCtx(),
77
72
  );
78
- expect(isError(wrongZip)).toBe(true);
73
+ expect(isToolFailure(wrongZip)).toBe(true);
79
74
  });
80
75
 
81
76
  test("two customers share a first name — the zip is what separates them", async () => {
82
77
  const a = (await findUserIdByNameZip.execute(
83
78
  { first_name: "Aarav", last_name: "Anderson", zip: "19031" },
84
79
  makeCtx(),
85
- )) as AuthResult | ErrorResult;
80
+ )) as AuthResult | ToolFailure;
86
81
  const b = (await findUserIdByNameZip.execute(
87
82
  { first_name: "Aarav", last_name: "Gonzalez", zip: "78268" },
88
83
  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");
84
+ )) as AuthResult | ToolFailure;
85
+ expect(isToolFailure(a) ? null : a.user_id).toBe("aarav_anderson_8794");
86
+ expect(isToolFailure(b) ? null : b.user_id).toBe("aarav_gonzalez_5113");
92
87
  });
93
88
 
94
89
  test("re-authenticating as the same customer is a no-op success", async () => {
95
90
  const ctx = await authedCtx("olivia.ito5204@example.com");
96
91
  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");
92
+ expect(isToolFailure(again)).toBe(false);
93
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
99
94
  });
100
95
 
101
96
  test("switching to a DIFFERENT customer mid-conversation is refused", async () => {
@@ -104,8 +99,8 @@ describe("authentication", () => {
104
99
  { email: "aarav.anderson9752@example.com" },
105
100
  ctx,
106
101
  );
107
- expect(isError(switched) && switched.error.toLowerCase()).toContain("one customer");
108
- expect(getState(ctx).authenticatedUserId).toBe("olivia_ito_3591");
102
+ expect(isToolFailure(switched) && switched.error.toLowerCase()).toContain("one customer");
103
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
109
104
  });
110
105
 
111
106
  test("switching via name + zip is refused too — both doors, one lock", async () => {
@@ -114,8 +109,8 @@ describe("authentication", () => {
114
109
  { first_name: "Aarav", last_name: "Anderson", zip: "19031" },
115
110
  ctx,
116
111
  );
117
- expect(isError(switched)).toBe(true);
118
- expect(getState(ctx).authenticatedUserId).toBe("olivia_ito_3591");
112
+ expect(isToolFailure(switched)).toBe(true);
113
+ expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
119
114
  });
120
115
  });
121
116
 
@@ -149,8 +144,8 @@ describe("read tools", () => {
149
144
  const ctx = await authedCtx("olivia.ito5204@example.com");
150
145
  const result = (await getUserDetails.execute({ user_id: "olivia_ito_3591" }, ctx)) as
151
146
  | UserDetailsResult
152
- | ErrorResult;
153
- if (isError(result)) throw new Error(result.error);
147
+ | ToolFailure;
148
+ if (isToolFailure(result)) throw new Error(result.error);
154
149
  expect(result.email).toBe("olivia.ito5204@example.com");
155
150
  expect(result.orders).toHaveLength(5);
156
151
  const card = result.payment_methods.find((m) => m.payment_method_id === "gift_card_7794233");
@@ -160,30 +155,30 @@ describe("read tools", () => {
160
155
  test("get_user_details refuses a different user id", async () => {
161
156
  const ctx = await authedCtx("olivia.ito5204@example.com");
162
157
  const result = await getUserDetails.execute({ user_id: "aarav_anderson_8794" }, ctx);
163
- expect(isError(result)).toBe(true);
158
+ expect(isToolFailure(result)).toBe(true);
164
159
  });
165
160
 
166
161
  test("get_order_details resolves shorthand and sets focus", async () => {
167
162
  const ctx = await authedCtx("olivia.ito5204@example.com");
168
163
  const result = (await getOrderDetails.execute({ order_id: "the delivered one" }, ctx)) as
169
164
  | OrderDetailsResult
170
- | ErrorResult;
171
- if (isError(result)) throw new Error(result.error);
165
+ | ToolFailure;
166
+ if (isToolFailure(result)) throw new Error(result.error);
172
167
  expect(result.order_id).toBe("#W5866402");
173
- expect(getState(ctx).focus.orderId).toBe("#W5866402");
168
+ expect(retailSlot.get(ctx).focus.orderId).toBe("#W5866402");
174
169
  });
175
170
 
176
171
  test("get_order_details refuses another customer's order", async () => {
177
172
  const ctx = await authedCtx("olivia.ito5204@example.com");
178
173
  const result = await getOrderDetails.execute({ order_id: "#W4316152" }, ctx);
179
- expect(isError(result)).toBe(true);
174
+ expect(isToolFailure(result)).toBe(true);
180
175
  });
181
176
 
182
177
  test("get_product_details lists variants and needs no authentication", async () => {
183
178
  const result = (await getProductDetails.execute({ product_id: "9832717871" }, makeCtx())) as
184
179
  | ProductDetailsResult
185
- | ErrorResult;
186
- if (isError(result)) throw new Error(result.error);
180
+ | ToolFailure;
181
+ if (isToolFailure(result)) throw new Error(result.error);
187
182
  expect(result.name).toBe("Tea Kettle");
188
183
  expect(result.variants.length).toBeGreaterThan(1);
189
184
  expect(result.variants[0]).toHaveProperty("item_id");
@@ -191,14 +186,14 @@ describe("read tools", () => {
191
186
 
192
187
  test("get_product_details rejects an item id passed as a product id, and says so", async () => {
193
188
  const result = await getProductDetails.execute({ product_id: "3909406921" }, makeCtx());
194
- expect(isError(result) && result.error).toContain("item id");
189
+ expect(isToolFailure(result) && result.error).toContain("item id");
195
190
  });
196
191
 
197
192
  test("get_item_details resolves an item without knowing its product", async () => {
198
193
  const result = (await getItemDetails.execute({ item_id: "3909406921" }, makeCtx())) as
199
194
  | ItemDetailsResult
200
- | ErrorResult;
201
- if (isError(result)) throw new Error(result.error);
195
+ | ToolFailure;
196
+ if (isToolFailure(result)) throw new Error(result.error);
202
197
  expect(result.price).toBe(98.25);
203
198
  expect(result.product_name).toBe("Tea Kettle");
204
199
  });
@@ -206,8 +201,8 @@ describe("read tools", () => {
206
201
  test("list_all_product_types returns all 50, sorted by name", async () => {
207
202
  const result = (await listAllProductTypes.execute({}, makeCtx())) as
208
203
  | ProductTypesResult
209
- | ErrorResult;
210
- if (isError(result)) throw new Error(result.error);
204
+ | ToolFailure;
205
+ if (isToolFailure(result)) throw new Error(result.error);
211
206
  expect(Object.keys(result.products)).toHaveLength(50);
212
207
  const names = Object.keys(result.products);
213
208
  // localeCompare, not the default lexicographic sort — the tool sorts for a
@@ -232,12 +227,12 @@ describe("cancel_pending_order", () => {
232
227
  const result = (await cancelPendingOrder.execute(
233
228
  { order_id: "#W9300146", reason: "ordered by mistake" },
234
229
  ctx,
235
- )) as CancelOrderResult | ErrorResult;
236
- if (isError(result)) throw new Error(result.error);
230
+ )) as CancelOrderResult | ToolFailure;
231
+ if (isToolFailure(result)) throw new Error(result.error);
237
232
  expect(result.status).toBe("cancelled");
238
233
  expect(result.refund_immediate).toBe(true);
239
234
 
240
- const state = getState(ctx);
235
+ const state = retailSlot.get(ctx);
241
236
  expect(state.store.orders["#W9300146"]?.status).toBe("cancelled");
242
237
  expect(state.store.orders["#W9300146"]?.cancel_reason).toBe("ordered by mistake");
243
238
  // $17.00 card + the $153.23 order total.
@@ -248,7 +243,7 @@ describe("cancel_pending_order", () => {
248
243
  test("appends a matching refund to the payment history", async () => {
249
244
  const ctx = await authedCtx("aarav.anderson9752@example.com");
250
245
  await cancelPendingOrder.execute({ order_id: "#W9300146", reason: "no longer needed" }, ctx);
251
- const history = getState(ctx).store.orders["#W9300146"]?.payment_history ?? [];
246
+ const history = retailSlot.get(ctx).store.orders["#W9300146"]?.payment_history ?? [];
252
247
  expect(history).toHaveLength(2);
253
248
  expect(history[1]).toMatchObject({
254
249
  transaction_type: "refund",
@@ -262,11 +257,11 @@ describe("cancel_pending_order", () => {
262
257
  const result = (await cancelPendingOrder.execute(
263
258
  { order_id: "#W5442520", reason: "no longer needed" },
264
259
  ctx,
265
- )) as CancelOrderResult | ErrorResult;
266
- if (isError(result)) throw new Error(result.error);
260
+ )) as CancelOrderResult | ToolFailure;
261
+ if (isToolFailure(result)) throw new Error(result.error);
267
262
  expect(result.refund_immediate).toBe(false);
268
263
  expect(result.message).toContain("5 to 7");
269
- const card = getState(ctx).store.users.olivia_ito_3591?.payment_methods.gift_card_7794233;
264
+ const card = retailSlot.get(ctx).store.users.olivia_ito_3591?.payment_methods.gift_card_7794233;
270
265
  expect(card?.source === "gift_card" && card.balance).toBe(56);
271
266
  });
272
267
 
@@ -276,8 +271,8 @@ describe("cancel_pending_order", () => {
276
271
  { order_id: "#W5353646", reason: "no longer needed" },
277
272
  ctx,
278
273
  );
279
- expect(isError(result) && result.error).toContain("processed");
280
- expect(getState(ctx).store.orders["#W5353646"]?.status).toBe("processed");
274
+ expect(isToolFailure(result) && result.error).toContain("processed");
275
+ expect(retailSlot.get(ctx).store.orders["#W5353646"]?.status).toBe("processed");
281
276
  });
282
277
 
283
278
  test("refuses a delivered order", async () => {
@@ -286,7 +281,7 @@ describe("cancel_pending_order", () => {
286
281
  { order_id: "#W5866402", reason: "no longer needed" },
287
282
  ctx,
288
283
  );
289
- expect(isError(result)).toBe(true);
284
+ expect(isToolFailure(result)).toBe(true);
290
285
  });
291
286
 
292
287
  test("refuses a reason outside tau2's two accepted values", async () => {
@@ -298,8 +293,8 @@ describe("cancel_pending_order", () => {
298
293
  { order_id: "#W9300146", reason: "changed my mind" as unknown as "no longer needed" },
299
294
  ctx,
300
295
  );
301
- expect(isError(result) && result.error).toContain("no longer needed");
302
- expect(getState(ctx).store.orders["#W9300146"]?.status).toBe("pending");
296
+ expect(isToolFailure(result) && result.error).toContain("no longer needed");
297
+ expect(retailSlot.get(ctx).store.orders["#W9300146"]?.status).toBe("pending");
303
298
  });
304
299
 
305
300
  test("refuses cancelling twice", async () => {
@@ -309,8 +304,9 @@ describe("cancel_pending_order", () => {
309
304
  { order_id: "#W9300146", reason: "no longer needed" },
310
305
  ctx,
311
306
  );
312
- expect(isError(second)).toBe(true);
313
- const card = getState(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
307
+ expect(isToolFailure(second)).toBe(true);
308
+ const card =
309
+ retailSlot.get(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
314
310
  expect(card?.source === "gift_card" && card.balance).toBe(170.23);
315
311
  });
316
312
 
@@ -319,8 +315,8 @@ describe("cancel_pending_order", () => {
319
315
  const result = (await cancelPendingOrder.execute(
320
316
  { order_id: "my pending order", reason: "ordered by mistake" },
321
317
  ctx,
322
- )) as CancelOrderResult | ErrorResult;
323
- expect(isError(result) ? null : result.order_id).toBe("#W9300146");
318
+ )) as CancelOrderResult | ToolFailure;
319
+ expect(isToolFailure(result) ? null : result.order_id).toBe("#W9300146");
324
320
  });
325
321
 
326
322
  test("refuses ambiguous shorthand rather than cancelling the wrong order", async () => {
@@ -329,9 +325,9 @@ describe("cancel_pending_order", () => {
329
325
  { order_id: "my pending order", reason: "no longer needed" },
330
326
  ctx,
331
327
  );
332
- expect(isError(result)).toBe(true);
328
+ expect(isToolFailure(result)).toBe(true);
333
329
  const statuses = ["#W5442520", "#W7941031", "#W3657213"].map(
334
- (id) => getState(ctx).store.orders[id]?.status,
330
+ (id) => retailSlot.get(ctx).store.orders[id]?.status,
335
331
  );
336
332
  expect(statuses).toEqual(["pending", "pending", "pending"]);
337
333
  });
@@ -357,28 +353,28 @@ describe("modify_pending_order_address", () => {
357
353
  const result = (await modifyPendingOrderAddress.execute(
358
354
  { order_id: "#W9300146", ...NEW_ADDRESS },
359
355
  ctx,
360
- )) as AddressToolResult | ErrorResult;
361
- if (isError(result)) throw new Error(result.error);
356
+ )) as AddressToolResult | ToolFailure;
357
+ if (isToolFailure(result)) throw new Error(result.error);
362
358
  expect(result.address).toEqual(NEW_ADDRESS);
363
- expect(getState(ctx).store.orders["#W9300146"]?.address).toEqual(NEW_ADDRESS);
359
+ expect(retailSlot.get(ctx).store.orders["#W9300146"]?.address).toEqual(NEW_ADDRESS);
364
360
  });
365
361
 
366
362
  test("leaves the customer's default address untouched", async () => {
367
363
  const ctx = await authedCtx("aarav.anderson9752@example.com");
368
364
  await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx);
369
- expect(getState(ctx).store.users.aarav_anderson_8794?.address.zip).toBe("19031");
365
+ expect(retailSlot.get(ctx).store.users.aarav_anderson_8794?.address.zip).toBe("19031");
370
366
  });
371
367
 
372
368
  test("accepts a 'pending (item modified)' order — unlike cancel", async () => {
373
369
  const ctx = await authedCtx("aarav.anderson9752@example.com");
374
- const order = getState(ctx).store.orders["#W9300146"];
370
+ const order = retailSlot.get(ctx).store.orders["#W9300146"];
375
371
  if (!order) throw new Error("fixture missing");
376
372
  order.status = "pending (item modified)";
377
373
  const result = await modifyPendingOrderAddress.execute(
378
374
  { order_id: "#W9300146", ...NEW_ADDRESS },
379
375
  ctx,
380
376
  );
381
- expect(isError(result)).toBe(false);
377
+ expect(isToolFailure(result)).toBe(false);
382
378
  });
383
379
 
384
380
  test("refuses a delivered order", async () => {
@@ -387,7 +383,7 @@ describe("modify_pending_order_address", () => {
387
383
  { order_id: "#W5866402", ...NEW_ADDRESS },
388
384
  ctx,
389
385
  );
390
- expect(isError(result)).toBe(true);
386
+ expect(isToolFailure(result)).toBe(true);
391
387
  });
392
388
 
393
389
  test("refuses another customer's order", async () => {
@@ -396,7 +392,7 @@ describe("modify_pending_order_address", () => {
396
392
  { order_id: "#W9300146", ...NEW_ADDRESS },
397
393
  ctx,
398
394
  );
399
- expect(isError(result)).toBe(true);
395
+ expect(isToolFailure(result)).toBe(true);
400
396
  });
401
397
  });
402
398
 
@@ -407,15 +403,15 @@ describe("modify_user_address", () => {
407
403
  { user_id: "emma_smith_8564", ...NEW_ADDRESS },
408
404
  ctx,
409
405
  );
410
- if (isError(result)) throw new Error(result.error);
411
- expect(getState(ctx).store.users.emma_smith_8564?.address).toEqual(NEW_ADDRESS);
406
+ if (isToolFailure(result)) throw new Error(result.error);
407
+ expect(retailSlot.get(ctx).store.users.emma_smith_8564?.address).toEqual(NEW_ADDRESS);
412
408
  });
413
409
 
414
410
  test("leaves existing orders' addresses untouched", async () => {
415
411
  const ctx = await authedCtx("emma.smith3991@example.com");
416
- const before = structuredClone(getState(ctx).store.orders["#W2417020"]?.address);
412
+ const before = structuredClone(retailSlot.get(ctx).store.orders["#W2417020"]?.address);
417
413
  await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx);
418
- expect(getState(ctx).store.orders["#W2417020"]?.address).toEqual(before);
414
+ expect(retailSlot.get(ctx).store.orders["#W2417020"]?.address).toEqual(before);
419
415
  });
420
416
 
421
417
  test("refuses a different user id", async () => {
@@ -424,8 +420,8 @@ describe("modify_user_address", () => {
424
420
  { user_id: "olivia_ito_3591", ...NEW_ADDRESS },
425
421
  ctx,
426
422
  );
427
- expect(isError(result)).toBe(true);
428
- expect(getState(ctx).store.users.olivia_ito_3591?.address.zip).toBe("80218");
423
+ expect(isToolFailure(result)).toBe(true);
424
+ expect(retailSlot.get(ctx).store.users.olivia_ito_3591?.address.zip).toBe("80218");
429
425
  });
430
426
 
431
427
  test("requires authentication", async () => {
@@ -433,7 +429,7 @@ describe("modify_user_address", () => {
433
429
  { user_id: "emma_smith_8564", ...NEW_ADDRESS },
434
430
  makeCtx(),
435
431
  );
436
- expect(isError(result) && result.error).toContain("find_user_id_by_email");
432
+ expect(isToolFailure(result) && result.error).toContain("find_user_id_by_email");
437
433
  });
438
434
  });
439
435
 
@@ -456,12 +452,12 @@ describe("modify_pending_order_items", () => {
456
452
  payment_method_id: "gift_card_8541487",
457
453
  },
458
454
  ctx,
459
- )) as ModifyItemsResult | ErrorResult;
460
- if (isError(result)) throw new Error(result.error);
455
+ )) as ModifyItemsResult | ToolFailure;
456
+ if (isToolFailure(result)) throw new Error(result.error);
461
457
  expect(result.price_difference).toBe(-382.03);
462
458
  expect(result.status).toBe("pending (item modified)");
463
459
 
464
- const state = getState(ctx);
460
+ const state = retailSlot.get(ctx);
465
461
  const order = state.store.orders["#W2417020"];
466
462
  expect(order?.items[0]?.item_id).toBe("6017636844");
467
463
  expect(order?.items[0]?.price).toBe(2292.37);
@@ -481,7 +477,7 @@ describe("modify_pending_order_items", () => {
481
477
  },
482
478
  ctx,
483
479
  );
484
- const history = getState(ctx).store.orders["#W2417020"]?.payment_history ?? [];
480
+ const history = retailSlot.get(ctx).store.orders["#W2417020"]?.payment_history ?? [];
485
481
  expect(history[1]).toMatchObject({
486
482
  transaction_type: "refund",
487
483
  amount: 382.03,
@@ -509,7 +505,7 @@ describe("modify_pending_order_items", () => {
509
505
  },
510
506
  ctx,
511
507
  );
512
- expect(isError(second) && second.error).toContain("pending (item modified)");
508
+ expect(isToolFailure(second) && second.error).toContain("pending (item modified)");
513
509
  });
514
510
 
515
511
  test("a modified order can no longer be cancelled", async () => {
@@ -527,7 +523,7 @@ describe("modify_pending_order_items", () => {
527
523
  { order_id: "#W2417020", reason: "no longer needed" },
528
524
  ctx,
529
525
  );
530
- expect(isError(cancelled)).toBe(true);
526
+ expect(isToolFailure(cancelled)).toBe(true);
531
527
  });
532
528
 
533
529
  test("refuses when the gift card cannot cover the difference, changing nothing", async () => {
@@ -545,9 +541,9 @@ describe("modify_pending_order_items", () => {
545
541
  },
546
542
  ctx,
547
543
  );
548
- expect(isError(result) && result.error.toLowerCase()).toContain("balance");
544
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("balance");
549
545
 
550
- const state = getState(ctx);
546
+ const state = retailSlot.get(ctx);
551
547
  const order = state.store.orders["#W6436609"];
552
548
  expect(order?.status).toBe("pending");
553
549
  expect(order?.items.map((i) => i.item_id)).toContain("6017636844");
@@ -566,10 +562,10 @@ describe("modify_pending_order_items", () => {
566
562
  payment_method_id: "credit_card_8955149",
567
563
  },
568
564
  ctx,
569
- )) as ModifyItemsResult | ErrorResult;
570
- if (isError(result)) throw new Error(result.error);
565
+ )) as ModifyItemsResult | ToolFailure;
566
+ if (isToolFailure(result)) throw new Error(result.error);
571
567
  expect(result.price_difference).toBe(167.37);
572
- const history = getState(ctx).store.orders["#W6436609"]?.payment_history ?? [];
568
+ const history = retailSlot.get(ctx).store.orders["#W6436609"]?.payment_history ?? [];
573
569
  expect(history[1]).toMatchObject({
574
570
  transaction_type: "payment",
575
571
  amount: 167.37,
@@ -588,7 +584,7 @@ describe("modify_pending_order_items", () => {
588
584
  },
589
585
  ctx,
590
586
  );
591
- expect(isError(result)).toBe(true);
587
+ expect(isToolFailure(result)).toBe(true);
592
588
  });
593
589
 
594
590
  test("refuses a payment method belonging to another customer", async () => {
@@ -602,8 +598,8 @@ describe("modify_pending_order_items", () => {
602
598
  },
603
599
  ctx,
604
600
  );
605
- expect(isError(result)).toBe(true);
606
- expect(getState(ctx).store.orders["#W2417020"]?.status).toBe("pending");
601
+ expect(isToolFailure(result)).toBe(true);
602
+ expect(retailSlot.get(ctx).store.orders["#W2417020"]?.status).toBe("pending");
607
603
  });
608
604
  });
609
605
 
@@ -623,11 +619,11 @@ describe("modify_pending_order_payment", () => {
623
619
  const result = (await modifyPendingOrderPayment.execute(
624
620
  { order_id: "#W5442520", payment_method_id: "paypal_8049766" },
625
621
  ctx,
626
- )) as ModifyPaymentResult | ErrorResult;
627
- if (isError(result)) throw new Error(result.error);
622
+ )) as ModifyPaymentResult | ToolFailure;
623
+ if (isToolFailure(result)) throw new Error(result.error);
628
624
  expect(result.status).toBe("pending");
629
625
 
630
- const history = getState(ctx).store.orders["#W5442520"]?.payment_history ?? [];
626
+ const history = retailSlot.get(ctx).store.orders["#W5442520"]?.payment_history ?? [];
631
627
  expect(history).toHaveLength(3);
632
628
  expect(history[1]).toMatchObject({
633
629
  transaction_type: "payment",
@@ -648,8 +644,9 @@ describe("modify_pending_order_payment", () => {
648
644
  { order_id: "#W9160732", payment_method_id: "paypal_6121064" },
649
645
  ctx,
650
646
  );
651
- expect(isError(result)).toBe(false);
652
- const card = getState(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
647
+ expect(isToolFailure(result)).toBe(false);
648
+ const card =
649
+ retailSlot.get(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
653
650
  expect(card?.source === "gift_card" && card.balance).toBe(1107.54);
654
651
  });
655
652
 
@@ -660,10 +657,11 @@ describe("modify_pending_order_payment", () => {
660
657
  { order_id: "#W6979932", payment_method_id: "gift_card_5979071" },
661
658
  ctx,
662
659
  );
663
- expect(isError(result) && result.error.toLowerCase()).toContain("balance");
664
- const card = getState(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
660
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("balance");
661
+ const card =
662
+ retailSlot.get(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
665
663
  expect(card?.source === "gift_card" && card.balance).toBe(96);
666
- expect(getState(ctx).store.orders["#W6979932"]?.payment_history).toHaveLength(1);
664
+ expect(retailSlot.get(ctx).store.orders["#W6979932"]?.payment_history).toHaveLength(1);
667
665
  });
668
666
 
669
667
  test("refuses the method the order already uses", async () => {
@@ -672,12 +670,12 @@ describe("modify_pending_order_payment", () => {
672
670
  { order_id: "#W5442520", payment_method_id: "credit_card_9753331" },
673
671
  ctx,
674
672
  );
675
- expect(isError(result) && result.error.toLowerCase()).toContain("different");
673
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("different");
676
674
  });
677
675
 
678
676
  test("refuses an order whose payment history is not a single payment", async () => {
679
677
  const ctx = await authedCtx("olivia.ito5204@example.com");
680
- const order = getState(ctx).store.orders["#W5442520"];
678
+ const order = retailSlot.get(ctx).store.orders["#W5442520"];
681
679
  if (!order) throw new Error("fixture missing");
682
680
  // No seeded pending order has a second history entry, so construct one —
683
681
  // tau2 guards this case and the guard should still be covered.
@@ -690,7 +688,7 @@ describe("modify_pending_order_payment", () => {
690
688
  { order_id: "#W5442520", payment_method_id: "paypal_8049766" },
691
689
  ctx,
692
690
  );
693
- expect(isError(result) && result.error.toLowerCase()).toContain("exactly one payment");
691
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("exactly one payment");
694
692
  });
695
693
 
696
694
  test("refuses a delivered order", async () => {
@@ -699,7 +697,7 @@ describe("modify_pending_order_payment", () => {
699
697
  { order_id: "#W5866402", payment_method_id: "gift_card_7794233" },
700
698
  ctx,
701
699
  );
702
- expect(isError(result)).toBe(true);
700
+ expect(isToolFailure(result)).toBe(true);
703
701
  });
704
702
 
705
703
  test("refuses a method not on the customer's profile", async () => {
@@ -708,7 +706,7 @@ describe("modify_pending_order_payment", () => {
708
706
  { order_id: "#W5442520", payment_method_id: "gift_card_7245904" },
709
707
  ctx,
710
708
  );
711
- expect(isError(result)).toBe(true);
709
+ expect(isToolFailure(result)).toBe(true);
712
710
  });
713
711
  });
714
712
 
@@ -739,10 +737,10 @@ describe("return_delivered_order_items", () => {
739
737
  payment_method_id: "paypal_2306935",
740
738
  },
741
739
  ctx,
742
- )) as ReturnResult | ErrorResult;
743
- if (isError(result)) throw new Error(result.error);
740
+ )) as ReturnResult | ToolFailure;
741
+ if (isToolFailure(result)) throw new Error(result.error);
744
742
  expect(result.status).toBe("return requested");
745
- const order = getState(ctx).store.orders["#W1840144"];
743
+ const order = retailSlot.get(ctx).store.orders["#W1840144"];
746
744
  expect(order?.return_items).toEqual(["8590708195"]);
747
745
  expect(order?.return_payment_method_id).toBe("paypal_2306935");
748
746
  });
@@ -758,7 +756,7 @@ describe("return_delivered_order_items", () => {
758
756
  },
759
757
  ctx,
760
758
  );
761
- expect(isError(result)).toBe(false);
759
+ expect(isToolFailure(result)).toBe(false);
762
760
  });
763
761
 
764
762
  test("refuses a non-original, non-gift-card method", async () => {
@@ -772,8 +770,8 @@ describe("return_delivered_order_items", () => {
772
770
  },
773
771
  ctx,
774
772
  );
775
- expect(isError(result) && result.error.toLowerCase()).toContain("original");
776
- expect(getState(ctx).store.orders["#W1840144"]?.status).toBe("delivered");
773
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("original");
774
+ expect(retailSlot.get(ctx).store.orders["#W1840144"]?.status).toBe("delivered");
777
775
  });
778
776
 
779
777
  test("returns both copies of a duplicate item and sorts the recorded list", async () => {
@@ -786,8 +784,8 @@ describe("return_delivered_order_items", () => {
786
784
  },
787
785
  ctx,
788
786
  );
789
- expect(isError(result)).toBe(false);
790
- expect(getState(ctx).store.orders["#W1840144"]?.return_items).toEqual([
787
+ expect(isToolFailure(result)).toBe(false);
788
+ expect(retailSlot.get(ctx).store.orders["#W1840144"]?.return_items).toEqual([
791
789
  "6534134392",
792
790
  "8590708195",
793
791
  "8590708195",
@@ -804,7 +802,7 @@ describe("return_delivered_order_items", () => {
804
802
  },
805
803
  ctx,
806
804
  );
807
- expect(isError(result)).toBe(true);
805
+ expect(isToolFailure(result)).toBe(true);
808
806
  });
809
807
 
810
808
  test("refuses a pending order and refuses a second return", async () => {
@@ -817,7 +815,7 @@ describe("return_delivered_order_items", () => {
817
815
  },
818
816
  ctx,
819
817
  );
820
- expect(isError(pending)).toBe(true);
818
+ expect(isToolFailure(pending)).toBe(true);
821
819
 
822
820
  await returnDeliveredOrderItems.execute(
823
821
  { order_id: "#W1840144", item_ids: ["8590708195"], payment_method_id: "paypal_2306935" },
@@ -827,7 +825,7 @@ describe("return_delivered_order_items", () => {
827
825
  { order_id: "#W1840144", item_ids: ["6534134392"], payment_method_id: "paypal_2306935" },
828
826
  ctx,
829
827
  );
830
- expect(isError(again) && again.error).toContain("return requested");
828
+ expect(isToolFailure(again) && again.error).toContain("return requested");
831
829
  });
832
830
  });
833
831
 
@@ -842,10 +840,10 @@ describe("exchange_delivered_order_items", () => {
842
840
  payment_method_id: "gift_card_7245904",
843
841
  },
844
842
  ctx,
845
- )) as ExchangeResult | ErrorResult;
846
- if (isError(result)) throw new Error(result.error);
843
+ )) as ExchangeResult | ToolFailure;
844
+ if (isToolFailure(result)) throw new Error(result.error);
847
845
  expect(result.price_difference).toBe(3.45);
848
- const order = getState(ctx).store.orders["#W4316152"];
846
+ const order = retailSlot.get(ctx).store.orders["#W4316152"];
849
847
  expect(order?.status).toBe("exchange requested");
850
848
  expect(order?.exchange_items).toEqual(["7292993796"]);
851
849
  expect(order?.exchange_new_items).toEqual(["3909406921"]);
@@ -863,7 +861,8 @@ describe("exchange_delivered_order_items", () => {
863
861
  },
864
862
  ctx,
865
863
  );
866
- const card = getState(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
864
+ const card =
865
+ retailSlot.get(ctx).store.users.aarav_anderson_8794?.payment_methods.gift_card_7245904;
867
866
  expect(card?.source === "gift_card" && card.balance).toBe(17);
868
867
  });
869
868
 
@@ -878,7 +877,7 @@ describe("exchange_delivered_order_items", () => {
878
877
  },
879
878
  ctx,
880
879
  );
881
- expect(getState(ctx).store.orders["#W4316152"]?.items.map((i) => i.item_id)).toEqual([
880
+ expect(retailSlot.get(ctx).store.orders["#W4316152"]?.items.map((i) => i.item_id)).toEqual([
882
881
  "7292993796",
883
882
  "7292993796",
884
883
  ]);
@@ -894,8 +893,8 @@ describe("exchange_delivered_order_items", () => {
894
893
  payment_method_id: "gift_card_7245904",
895
894
  },
896
895
  ctx,
897
- )) as ExchangeResult | ErrorResult;
898
- expect(isError(result) ? null : result.price_difference).toBe(6.9);
896
+ )) as ExchangeResult | ToolFailure;
897
+ expect(isToolFailure(result) ? null : result.price_difference).toBe(6.9);
899
898
  });
900
899
 
901
900
  test("records a negative difference as a refund direction", async () => {
@@ -908,8 +907,8 @@ describe("exchange_delivered_order_items", () => {
908
907
  payment_method_id: "gift_card_7794233",
909
908
  },
910
909
  ctx,
911
- )) as ExchangeResult | ErrorResult;
912
- expect(isError(result) ? null : result.price_difference).toBe(-40.86);
910
+ )) as ExchangeResult | ToolFailure;
911
+ expect(isToolFailure(result) ? null : result.price_difference).toBe(-40.86);
913
912
  });
914
913
 
915
914
  test("refuses a gift card that cannot cover the difference", async () => {
@@ -923,8 +922,8 @@ describe("exchange_delivered_order_items", () => {
923
922
  },
924
923
  ctx,
925
924
  );
926
- expect(isError(result) && result.error.toLowerCase()).toContain("balance");
927
- expect(getState(ctx).store.orders["#W9311069"]?.status).toBe("delivered");
925
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("balance");
926
+ expect(retailSlot.get(ctx).store.orders["#W9311069"]?.status).toBe("delivered");
928
927
  });
929
928
 
930
929
  test("refuses a cross-product swap", async () => {
@@ -938,7 +937,7 @@ describe("exchange_delivered_order_items", () => {
938
937
  },
939
938
  ctx,
940
939
  );
941
- expect(isError(result)).toBe(true);
940
+ expect(isToolFailure(result)).toBe(true);
942
941
  });
943
942
 
944
943
  test("refuses an unavailable target", async () => {
@@ -952,7 +951,7 @@ describe("exchange_delivered_order_items", () => {
952
951
  },
953
952
  ctx,
954
953
  );
955
- expect(isError(result) && result.error.toLowerCase()).toContain("not available");
954
+ expect(isToolFailure(result) && result.error.toLowerCase()).toContain("not available");
956
955
  });
957
956
 
958
957
  test("refuses a pending order and refuses a second exchange", async () => {
@@ -966,7 +965,7 @@ describe("exchange_delivered_order_items", () => {
966
965
  },
967
966
  ctx,
968
967
  );
969
- expect(isError(pending)).toBe(true);
968
+ expect(isToolFailure(pending)).toBe(true);
970
969
 
971
970
  await exchangeDeliveredOrderItems.execute(
972
971
  {
@@ -986,7 +985,7 @@ describe("exchange_delivered_order_items", () => {
986
985
  },
987
986
  ctx,
988
987
  );
989
- expect(isError(again)).toBe(true);
988
+ expect(isToolFailure(again)).toBe(true);
990
989
  });
991
990
 
992
991
  test("a returned order can no longer be exchanged", async () => {
@@ -1008,7 +1007,7 @@ describe("exchange_delivered_order_items", () => {
1008
1007
  },
1009
1008
  ctx,
1010
1009
  );
1011
- expect(isError(result)).toBe(true);
1010
+ expect(isToolFailure(result)).toBe(true);
1012
1011
  });
1013
1012
  });
1014
1013
 
@@ -1023,8 +1022,8 @@ describe("transfer_to_human_agents", () => {
1023
1022
  const result = (await transferToHumanAgents.execute(
1024
1023
  { summary: "Caller wants to dispute a charge from 2019." },
1025
1024
  makeCtx(),
1026
- )) as TransferResult | ErrorResult;
1027
- expect(isError(result)).toBe(false);
1028
- expect(isError(result) ? null : result.transferred).toBe(true);
1025
+ )) as TransferResult | ToolFailure;
1026
+ expect(isToolFailure(result)).toBe(false);
1027
+ expect(isToolFailure(result) ? null : result.transferred).toBe(true);
1029
1028
  });
1030
1029
  });