@alexkroman1/aai-cli 7.0.0 → 8.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 (122) hide show
  1. package/README.md +1 -0
  2. package/dist/{_bundler-B4RqNF5Z.mjs → _bundler-CDuYl5Gb.mjs} +1 -1
  3. package/dist/{_dev-server-CiKFH9qw.mjs → _dev-server-Dt3TCjhj.mjs} +6 -2
  4. package/dist/{_init-CE4HKdgV.mjs → _init-BhEfQ6Yi.mjs} +1 -1
  5. package/dist/_resource-commands.d.ts +23 -0
  6. package/dist/{_slug-api-nRFaBEPJ.mjs → _slug-api-z-USxPtL.mjs} +14 -10
  7. package/dist/_slug-api.d.ts +5 -2
  8. package/dist/{_templates-FI3xPEMj.mjs → _templates-4WcKOjS5.mjs} +4 -5
  9. package/dist/_templates.d.ts +3 -4
  10. package/dist/{build-6jfaakgc.mjs → build-BBxf8vom.mjs} +2 -2
  11. package/dist/cli.mjs +338 -346
  12. package/dist/{deploy-DvYeD8ia.mjs → deploy-DRC8jWr8.mjs} +1 -1
  13. package/dist/{dev-B59JIfWO.mjs → dev-QuuOOUtv.mjs} +1 -1
  14. package/dist/eval-DfQ8cm5p.mjs +56 -0
  15. package/dist/eval.d.ts +43 -0
  16. package/dist/{init-CpJVjEg0.mjs → init-CUo87gcI.mjs} +2 -2
  17. package/dist/{logs-9q-psa4q.mjs → logs-CQ2T46dr.mjs} +1 -1
  18. package/dist/scaffold/CLAUDE.md +409 -68
  19. package/dist/scaffold/package.json +5 -4
  20. package/dist/scaffold/server.mjs +10 -3
  21. package/dist/scaffold/tsconfig.json +0 -1
  22. package/dist/{secret-CVvSLIDV.mjs → secret-Ct34kdgU.mjs} +1 -1
  23. package/dist/{storage-BvUrnvM3.mjs → storage-i_79ZwAR.mjs} +1 -1
  24. package/dist/{studio--MUV0cid.mjs → studio-CzTzPqe1.mjs} +1 -1
  25. package/dist/templates/briefing-desk/agent.test.ts +350 -0
  26. package/dist/templates/briefing-desk/agent.ts +30 -0
  27. package/dist/templates/briefing-desk/shared.ts +200 -0
  28. package/dist/templates/briefing-desk/system-prompt.md +39 -0
  29. package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
  30. package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
  31. package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
  32. package/dist/templates/call-audit/agent.eval.test.ts +474 -0
  33. package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
  34. package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
  35. package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
  36. package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
  37. package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
  38. package/dist/templates/infocom-adventure/agent.test.ts +49 -18
  39. package/dist/templates/infocom-adventure/agent.ts +21 -0
  40. package/dist/templates/infocom-adventure/shared.ts +20 -9
  41. package/dist/templates/infocom-adventure/system-prompt.md +1 -1
  42. package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
  43. package/dist/templates/link-digest/agent.eval.test.ts +195 -0
  44. package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
  45. package/dist/templates/night-owl/agent.eval.test.ts +272 -0
  46. package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
  47. package/dist/templates/personal-finance/system-prompt.md +4 -0
  48. package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
  49. package/dist/templates/pipeline-simple/agent.ts +2 -2
  50. package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
  51. package/dist/templates/pizza-ordering/agent.test.ts +15 -0
  52. package/dist/templates/pizza-ordering/shared.ts +20 -1
  53. package/dist/templates/pizza-ordering/system-prompt.md +6 -0
  54. package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
  55. package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
  56. package/dist/templates/podcast-digest/agent.test.ts +30 -59
  57. package/dist/templates/podcast-digest/agent.ts +1 -1
  58. package/dist/templates/podcast-digest/workflows/digest.ts +52 -19
  59. package/dist/templates/podcast-digest/workflows/slack.ts +48 -169
  60. package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
  61. package/dist/templates/redline/agent.eval.test.ts +209 -0
  62. package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
  63. package/dist/templates/retail/address.ts +87 -1
  64. package/dist/templates/retail/agent.eval.test.ts +314 -0
  65. package/dist/templates/retail/agent.test.ts +171 -91
  66. package/dist/templates/retail/agent.ts +35 -1
  67. package/dist/templates/retail/cancel.ts +116 -0
  68. package/dist/templates/retail/client.tsx +39 -0
  69. package/dist/templates/retail/payment.ts +107 -0
  70. package/dist/templates/retail/pending.test.ts +196 -0
  71. package/dist/templates/retail/pending.ts +137 -0
  72. package/dist/templates/retail/registry.test.ts +119 -8
  73. package/dist/templates/retail/returns.ts +102 -0
  74. package/dist/templates/retail/shared.test.ts +42 -0
  75. package/dist/templates/retail/shared.ts +35 -0
  76. package/dist/templates/retail/store.test.ts +6 -4
  77. package/dist/templates/retail/store.ts +113 -57
  78. package/dist/templates/retail/swap.test.ts +4 -4
  79. package/dist/templates/retail/swap.ts +248 -15
  80. package/dist/templates/retail/system-prompt.md +62 -30
  81. package/dist/templates/retail/tools/cancel_change.ts +33 -0
  82. package/dist/templates/retail/tools/cancel_pending_order.ts +15 -59
  83. package/dist/templates/retail/tools/confirm_change.ts +42 -0
  84. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +21 -66
  85. package/dist/templates/retail/tools/modify_pending_order_address.ts +12 -24
  86. package/dist/templates/retail/tools/modify_pending_order_items.ts +22 -60
  87. package/dist/templates/retail/tools/modify_pending_order_payment.ts +15 -77
  88. package/dist/templates/retail/tools/modify_user_address.ts +12 -15
  89. package/dist/templates/retail/tools/return_delivered_order_items.ts +16 -71
  90. package/dist/templates/retail/tools/transfer_to_human_agents.ts +4 -2
  91. package/dist/templates/simple/agent.eval.test.ts +54 -0
  92. package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
  93. package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
  94. package/dist/templates/support-line/agent.eval.test.ts +273 -0
  95. package/dist/templates/support-line/system-prompt.md +2 -1
  96. package/dist/templates/support-line/tools/answer_question.ts +56 -7
  97. package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
  98. package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
  99. package/dist/templates/travel-concierge/agent.test.ts +110 -10
  100. package/dist/templates/travel-concierge/agent.ts +18 -9
  101. package/dist/templates/travel-concierge/shared.ts +110 -9
  102. package/dist/templates/travel-concierge/system-prompt.md +7 -1
  103. package/dist/templates/travel-concierge/tools/book_car_rental.ts +6 -3
  104. package/dist/templates/travel-concierge/tools/book_excursion.ts +6 -3
  105. package/dist/templates/travel-concierge/tools/book_hotel.ts +6 -3
  106. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +6 -3
  107. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
  108. package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
  109. package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
  110. package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
  111. package/dist/templates/travel-concierge/tools/update_ticket.ts +6 -3
  112. package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
  113. package/dist/templates/web-researcher/system-prompt.md +15 -4
  114. package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
  115. package/dist/test.d.ts +29 -5
  116. package/dist/{worker-bundler-COxnqstQ.mjs → worker-bundler-CCVEDjm1.mjs} +45 -25
  117. package/dist/worker-bundler.mjs +1 -1
  118. package/dist/{workflow-D2AQf2Pl.mjs → workflow-BflATMmU.mjs} +19 -11
  119. package/package.json +5 -5
  120. package/dist/eject-C9WJyyr2.mjs +0 -116
  121. package/dist/eject.d.ts +0 -60
  122. package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
@@ -3,9 +3,11 @@ import { isToolFailure } from "@alexkroman1/aai";
3
3
  import { createToolContext, ok } from "@alexkroman1/aai/testing";
4
4
  import { describe, expect, test } from "vitest";
5
5
  import type { AuthResult } from "./authenticate.ts";
6
+ import type { StagedResult } from "./pending.ts";
6
7
  import type { Address } from "./shared.ts";
7
8
  import { callFlow, retailSlot } from "./store.ts";
8
9
  import cancelPendingOrder from "./tools/cancel_pending_order.ts";
10
+ import confirmChange from "./tools/confirm_change.ts";
9
11
  import exchangeDeliveredOrderItems from "./tools/exchange_delivered_order_items.ts";
10
12
  import findUserIdByEmail from "./tools/find_user_id_by_email.ts";
11
13
  import findUserIdByNameZip from "./tools/find_user_id_by_name_zip.ts";
@@ -34,6 +36,26 @@ async function authedCtx(email: string): Promise<ToolContext> {
34
36
  return ctx;
35
37
  }
36
38
 
39
+ /**
40
+ * Stage a change and say yes — the two calls one caller turn now produces.
41
+ *
42
+ * Nothing in this template mutates before `confirm_change`, so every spec about
43
+ * an APPLIED change has to run both halves. That is not helper ceremony: it is
44
+ * the property under test, and a spec that forgot the second call would find
45
+ * the store untouched. It asserts the readback exists on the way through,
46
+ * because a staged change the agent has no sentence for is the one shape that
47
+ * would let this gate pass while carrying nothing to confirm.
48
+ */
49
+ async function confirmed<R = unknown>(staged: unknown, ctx: ToolContext): Promise<R> {
50
+ const stage = ok<StagedResult>(staged);
51
+ // A throw rather than an `expect`, which biome's `noMisplacedAssertion`
52
+ // rightly refuses outside a test body. A staged change with no sentence to
53
+ // read back is the one shape that would let the gate pass while carrying
54
+ // nothing to confirm, so it stops the spec here rather than downstream.
55
+ if (stage.read_back.length === 0) throw new Error(`${stage.staged} staged with no readback`);
56
+ return ok<R>(await confirmChange.execute({}, ctx));
57
+ }
58
+
37
59
  describe("authentication", () => {
38
60
  test("find_user_id_by_email is case-insensitive and authenticates the session", async () => {
39
61
  const ctx = makeCtx();
@@ -43,7 +65,8 @@ describe("authentication", () => {
43
65
  expect(result.user_id).toBe("olivia_ito_3591");
44
66
  expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
45
67
  // Two facts, two homes: the store latches WHO, the flow holds WHETHER.
46
- expect(callFlow.position(ctx).state).toBe("serving");
68
+ // `serving` is a parent now, so an identified call reports its child.
69
+ expect(callFlow.position(ctx).state).toBe("serving.helping");
47
70
  });
48
71
 
49
72
  test("an unknown email is refused and leaves the session unauthenticated", async () => {
@@ -107,7 +130,7 @@ describe("authentication", () => {
107
130
  expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
108
131
  // The tool is legal in `serving` — the refusal comes from `authenticateAs`,
109
132
  // not from the gate — so the call stays exactly where it was.
110
- expect(callFlow.position(ctx).state).toBe("serving");
133
+ expect(callFlow.position(ctx).state).toBe("serving.helping");
111
134
  });
112
135
 
113
136
  test("switching via name + zip is refused too — both doors, one lock", async () => {
@@ -224,11 +247,12 @@ interface CancelOrderResult {
224
247
  describe("cancel_pending_order", () => {
225
248
  test("cancels a pending order and credits a gift card immediately", async () => {
226
249
  const ctx = await authedCtx("aarav.anderson9752@example.com");
227
- const result = ok<CancelOrderResult>(
250
+ const result = await confirmed<CancelOrderResult>(
228
251
  await cancelPendingOrder.execute(
229
252
  { order_id: "#W9300146", reason: "ordered by mistake" },
230
253
  ctx,
231
254
  ),
255
+ ctx,
232
256
  );
233
257
  expect(result.status).toBe("cancelled");
234
258
  expect(result.refund_immediate).toBe(true);
@@ -243,7 +267,10 @@ describe("cancel_pending_order", () => {
243
267
 
244
268
  test("appends a matching refund to the payment history", async () => {
245
269
  const ctx = await authedCtx("aarav.anderson9752@example.com");
246
- await cancelPendingOrder.execute({ order_id: "#W9300146", reason: "no longer needed" }, ctx);
270
+ await confirmed(
271
+ await cancelPendingOrder.execute({ order_id: "#W9300146", reason: "no longer needed" }, ctx),
272
+ ctx,
273
+ );
247
274
  const history = retailSlot.get(ctx).store.orders["#W9300146"]?.payment_history ?? [];
248
275
  expect(history).toHaveLength(2);
249
276
  expect(history[1]).toMatchObject({
@@ -255,8 +282,9 @@ describe("cancel_pending_order", () => {
255
282
 
256
283
  test("a non-gift-card refund takes 5-7 days and moves no balance", async () => {
257
284
  const ctx = await authedCtx("olivia.ito5204@example.com");
258
- const result = ok<CancelOrderResult>(
285
+ const result = await confirmed<CancelOrderResult>(
259
286
  await cancelPendingOrder.execute({ order_id: "#W5442520", reason: "no longer needed" }, ctx),
287
+ ctx,
260
288
  );
261
289
  expect(result.refund_immediate).toBe(false);
262
290
  expect(result.message).toContain("5 to 7");
@@ -298,7 +326,10 @@ describe("cancel_pending_order", () => {
298
326
 
299
327
  test("refuses cancelling twice", async () => {
300
328
  const ctx = await authedCtx("aarav.anderson9752@example.com");
301
- await cancelPendingOrder.execute({ order_id: "#W9300146", reason: "no longer needed" }, ctx);
329
+ await confirmed(
330
+ await cancelPendingOrder.execute({ order_id: "#W9300146", reason: "no longer needed" }, ctx),
331
+ ctx,
332
+ );
302
333
  const second = await cancelPendingOrder.execute(
303
334
  { order_id: "#W9300146", reason: "no longer needed" },
304
335
  ctx,
@@ -311,11 +342,12 @@ describe("cancel_pending_order", () => {
311
342
 
312
343
  test("resolves spoken shorthand to the single pending order", async () => {
313
344
  const ctx = await authedCtx("aarav.anderson9752@example.com");
314
- const result = ok<CancelOrderResult>(
345
+ const result = await confirmed<CancelOrderResult>(
315
346
  await cancelPendingOrder.execute(
316
347
  { order_id: "my pending order", reason: "ordered by mistake" },
317
348
  ctx,
318
349
  ),
350
+ ctx,
319
351
  );
320
352
  expect(result.order_id).toBe("#W9300146");
321
353
  });
@@ -351,8 +383,9 @@ interface AddressToolResult {
351
383
  describe("modify_pending_order_address", () => {
352
384
  test("rewrites a pending order's shipping address", async () => {
353
385
  const ctx = await authedCtx("aarav.anderson9752@example.com");
354
- const result = ok<AddressToolResult>(
386
+ const result = await confirmed<AddressToolResult>(
355
387
  await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx),
388
+ ctx,
356
389
  );
357
390
  expect(result.address).toEqual(NEW_ADDRESS);
358
391
  expect(retailSlot.get(ctx).store.orders["#W9300146"]?.address).toEqual(NEW_ADDRESS);
@@ -360,7 +393,10 @@ describe("modify_pending_order_address", () => {
360
393
 
361
394
  test("leaves the customer's default address untouched", async () => {
362
395
  const ctx = await authedCtx("aarav.anderson9752@example.com");
363
- await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx);
396
+ await confirmed(
397
+ await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx),
398
+ ctx,
399
+ );
364
400
  expect(retailSlot.get(ctx).store.users.aarav_anderson_8794?.address.zip).toBe("19031");
365
401
  });
366
402
 
@@ -400,14 +436,20 @@ describe("modify_pending_order_address", () => {
400
436
  describe("modify_user_address", () => {
401
437
  test("rewrites the customer's default address", async () => {
402
438
  const ctx = await authedCtx("emma.smith3991@example.com");
403
- ok(await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx));
439
+ await confirmed(
440
+ await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx),
441
+ ctx,
442
+ );
404
443
  expect(retailSlot.get(ctx).store.users.emma_smith_8564?.address).toEqual(NEW_ADDRESS);
405
444
  });
406
445
 
407
446
  test("leaves existing orders' addresses untouched", async () => {
408
447
  const ctx = await authedCtx("emma.smith3991@example.com");
409
448
  const before = structuredClone(retailSlot.get(ctx).store.orders["#W2417020"]?.address);
410
- await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx);
449
+ await confirmed(
450
+ await modifyUserAddress.execute({ user_id: "emma_smith_8564", ...NEW_ADDRESS }, ctx),
451
+ ctx,
452
+ );
411
453
  expect(retailSlot.get(ctx).store.orders["#W2417020"]?.address).toEqual(before);
412
454
  });
413
455
 
@@ -441,7 +483,7 @@ interface ModifyItemsResult {
441
483
  describe("modify_pending_order_items", () => {
442
484
  test("swaps an item, charges the difference to a gift card, and goes terminal", async () => {
443
485
  const ctx = await authedCtx("emma.smith3991@example.com");
444
- const result = ok<ModifyItemsResult>(
486
+ const result = await confirmed<ModifyItemsResult>(
445
487
  await modifyPendingOrderItems.execute(
446
488
  {
447
489
  order_id: "#W2417020",
@@ -451,6 +493,7 @@ describe("modify_pending_order_items", () => {
451
493
  },
452
494
  ctx,
453
495
  ),
496
+ ctx,
454
497
  );
455
498
  expect(result.price_difference).toBe(-382.03);
456
499
  expect(result.status).toBe("pending (item modified)");
@@ -466,13 +509,16 @@ describe("modify_pending_order_items", () => {
466
509
 
467
510
  test("records the difference as a refund entry when the new item is cheaper", async () => {
468
511
  const ctx = await authedCtx("emma.smith3991@example.com");
469
- await modifyPendingOrderItems.execute(
470
- {
471
- order_id: "#W2417020",
472
- item_ids: ["8997785118"],
473
- new_item_ids: ["6017636844"],
474
- payment_method_id: "gift_card_8541487",
475
- },
512
+ await confirmed(
513
+ await modifyPendingOrderItems.execute(
514
+ {
515
+ order_id: "#W2417020",
516
+ item_ids: ["8997785118"],
517
+ new_item_ids: ["6017636844"],
518
+ payment_method_id: "gift_card_8541487",
519
+ },
520
+ ctx,
521
+ ),
476
522
  ctx,
477
523
  );
478
524
  const history = retailSlot.get(ctx).store.orders["#W2417020"]?.payment_history ?? [];
@@ -485,13 +531,16 @@ describe("modify_pending_order_items", () => {
485
531
 
486
532
  test("refuses a second modification — the status is terminal", async () => {
487
533
  const ctx = await authedCtx("emma.smith3991@example.com");
488
- await modifyPendingOrderItems.execute(
489
- {
490
- order_id: "#W2417020",
491
- item_ids: ["8997785118"],
492
- new_item_ids: ["6017636844"],
493
- payment_method_id: "gift_card_8541487",
494
- },
534
+ await confirmed(
535
+ await modifyPendingOrderItems.execute(
536
+ {
537
+ order_id: "#W2417020",
538
+ item_ids: ["8997785118"],
539
+ new_item_ids: ["6017636844"],
540
+ payment_method_id: "gift_card_8541487",
541
+ },
542
+ ctx,
543
+ ),
495
544
  ctx,
496
545
  );
497
546
  const second = await modifyPendingOrderItems.execute(
@@ -508,13 +557,16 @@ describe("modify_pending_order_items", () => {
508
557
 
509
558
  test("a modified order can no longer be cancelled", async () => {
510
559
  const ctx = await authedCtx("emma.smith3991@example.com");
511
- await modifyPendingOrderItems.execute(
512
- {
513
- order_id: "#W2417020",
514
- item_ids: ["8997785118"],
515
- new_item_ids: ["6017636844"],
516
- payment_method_id: "gift_card_8541487",
517
- },
560
+ await confirmed(
561
+ await modifyPendingOrderItems.execute(
562
+ {
563
+ order_id: "#W2417020",
564
+ item_ids: ["8997785118"],
565
+ new_item_ids: ["6017636844"],
566
+ payment_method_id: "gift_card_8541487",
567
+ },
568
+ ctx,
569
+ ),
518
570
  ctx,
519
571
  );
520
572
  const cancelled = await cancelPendingOrder.execute(
@@ -552,7 +604,7 @@ describe("modify_pending_order_items", () => {
552
604
 
553
605
  test("the same upgrade succeeds on a payment method with no balance to run out of", async () => {
554
606
  const ctx = await authedCtx("anya.garcia2061@example.com");
555
- const result = ok<ModifyItemsResult>(
607
+ const result = await confirmed<ModifyItemsResult>(
556
608
  await modifyPendingOrderItems.execute(
557
609
  {
558
610
  order_id: "#W6436609",
@@ -562,6 +614,7 @@ describe("modify_pending_order_items", () => {
562
614
  },
563
615
  ctx,
564
616
  ),
617
+ ctx,
565
618
  );
566
619
  expect(result.price_difference).toBe(167.37);
567
620
  const history = retailSlot.get(ctx).store.orders["#W6436609"]?.payment_history ?? [];
@@ -615,11 +668,12 @@ describe("modify_pending_order_payment", () => {
615
668
  test("moves an order to a different method, refunding the old one", async () => {
616
669
  const ctx = await authedCtx("olivia.ito5204@example.com");
617
670
  // #W5442520 is $663.85 on credit_card_9753331. Pay by PayPal instead.
618
- const result = ok<ModifyPaymentResult>(
671
+ const result = await confirmed<ModifyPaymentResult>(
619
672
  await modifyPendingOrderPayment.execute(
620
673
  { order_id: "#W5442520", payment_method_id: "paypal_8049766" },
621
674
  ctx,
622
675
  ),
676
+ ctx,
623
677
  );
624
678
  expect(result.status).toBe("pending");
625
679
 
@@ -640,11 +694,13 @@ describe("modify_pending_order_payment", () => {
640
694
  test("refunds a gift card that was the original method", async () => {
641
695
  const ctx = await authedCtx("aarav.gonzalez9269@example.com");
642
696
  // #W9160732 is $1011.54 on gift_card_5979071 ($96.00). Move to PayPal.
643
- const result = await modifyPendingOrderPayment.execute(
644
- { order_id: "#W9160732", payment_method_id: "paypal_6121064" },
697
+ await confirmed(
698
+ await modifyPendingOrderPayment.execute(
699
+ { order_id: "#W9160732", payment_method_id: "paypal_6121064" },
700
+ ctx,
701
+ ),
645
702
  ctx,
646
703
  );
647
- expect(isToolFailure(result)).toBe(false);
648
704
  const card =
649
705
  retailSlot.get(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
650
706
  expect(card?.source === "gift_card" && card.balance).toBe(1107.54);
@@ -731,7 +787,7 @@ interface ExchangeResult {
731
787
  describe("return_delivered_order_items", () => {
732
788
  test("requests a return refunded to the original method", async () => {
733
789
  const ctx = await authedCtx("harper.brown3965@example.com");
734
- const result = ok<ReturnResult>(
790
+ const result = await confirmed<ReturnResult>(
735
791
  await returnDeliveredOrderItems.execute(
736
792
  {
737
793
  order_id: "#W1840144",
@@ -740,6 +796,7 @@ describe("return_delivered_order_items", () => {
740
796
  },
741
797
  ctx,
742
798
  ),
799
+ ctx,
743
800
  );
744
801
  expect(result.status).toBe("return requested");
745
802
  const order = retailSlot.get(ctx).store.orders["#W1840144"];
@@ -776,20 +833,25 @@ describe("return_delivered_order_items", () => {
776
833
  expect(retailSlot.get(ctx).store.orders["#W1840144"]?.status).toBe("delivered");
777
834
  });
778
835
 
779
- test("returns both copies of a duplicate item and sorts the recorded list", async () => {
836
+ test("returns both copies of a duplicate item, in the order they were named", async () => {
780
837
  const ctx = await authedCtx("harper.brown3965@example.com");
781
- const result = await returnDeliveredOrderItems.execute(
782
- {
783
- order_id: "#W1840144",
784
- item_ids: ["8590708195", "6534134392", "8590708195"],
785
- payment_method_id: "paypal_2306935",
786
- },
838
+ await confirmed(
839
+ await returnDeliveredOrderItems.execute(
840
+ {
841
+ order_id: "#W1840144",
842
+ item_ids: ["8590708195", "6534134392", "8590708195"],
843
+ payment_method_id: "paypal_2306935",
844
+ },
845
+ ctx,
846
+ ),
787
847
  ctx,
788
848
  );
789
- expect(isToolFailure(result)).toBe(false);
849
+ // Not sorted. tau2 sorted this list to match an expected end state and
850
+ // nothing compares against one any more, so it holds the caller's own
851
+ // order — which is the order the readback said them in.
790
852
  expect(retailSlot.get(ctx).store.orders["#W1840144"]?.return_items).toEqual([
791
- "6534134392",
792
853
  "8590708195",
854
+ "6534134392",
793
855
  "8590708195",
794
856
  ]);
795
857
  });
@@ -819,8 +881,11 @@ describe("return_delivered_order_items", () => {
819
881
  );
820
882
  expect(isToolFailure(pending)).toBe(true);
821
883
 
822
- await returnDeliveredOrderItems.execute(
823
- { order_id: "#W1840144", item_ids: ["8590708195"], payment_method_id: "paypal_2306935" },
884
+ await confirmed(
885
+ await returnDeliveredOrderItems.execute(
886
+ { order_id: "#W1840144", item_ids: ["8590708195"], payment_method_id: "paypal_2306935" },
887
+ ctx,
888
+ ),
824
889
  ctx,
825
890
  );
826
891
  const again = await returnDeliveredOrderItems.execute(
@@ -834,7 +899,7 @@ describe("return_delivered_order_items", () => {
834
899
  describe("exchange_delivered_order_items", () => {
835
900
  test("records an exchange with a positive price difference", async () => {
836
901
  const ctx = await authedCtx("aarav.anderson9752@example.com");
837
- const result = ok<ExchangeResult>(
902
+ const result = await confirmed<ExchangeResult>(
838
903
  await exchangeDeliveredOrderItems.execute(
839
904
  {
840
905
  order_id: "#W4316152",
@@ -844,6 +909,7 @@ describe("exchange_delivered_order_items", () => {
844
909
  },
845
910
  ctx,
846
911
  ),
912
+ ctx,
847
913
  );
848
914
  expect(result.price_difference).toBe(3.45);
849
915
  expect(result.exchanges).toEqual([
@@ -856,16 +922,16 @@ describe("exchange_delivered_order_items", () => {
856
922
  expect(order?.exchange_price_difference).toBe(3.45);
857
923
  });
858
924
 
859
- test("the ANSWER pairs by position even where the stored fields sort separately", async () => {
925
+ test("the stored fields hold the pairing that was priced, not two sorted sets", async () => {
860
926
  // `#W5866402` is the case that makes the difference visible: the two
861
- // requested replacements sort into the OTHER order, so the two stored lists
862
- // — tau2's `sorted(item_ids)` / `sorted(new_item_ids)`, kept for fidelity
863
- // with its expected end states read as espresso→sneaker if anything
864
- // treats them as a pairing. `planItemSwap` priced this positionally, so the
865
- // result has to report the pairing that was priced, not the one the sort
866
- // produced.
927
+ // requested replacements sort into the OTHER order. tau2 stored
928
+ // `sorted(item_ids)` / `sorted(new_item_ids)`, which read as
929
+ // espresso -> sneaker if anything treated them as a pairing — so the result
930
+ // had to carry the real pairing separately, and the stored fields were a
931
+ // trap for the next reader. Nothing is compared against a tau2 end state
932
+ // any more, so both hold what `planItemSwap` actually priced.
867
933
  const ctx = await authedCtx("olivia.ito5204@example.com");
868
- const result = ok<ExchangeResult>(
934
+ const result = await confirmed<ExchangeResult>(
869
935
  await exchangeDeliveredOrderItems.execute(
870
936
  {
871
937
  order_id: "#W5866402",
@@ -875,6 +941,7 @@ describe("exchange_delivered_order_items", () => {
875
941
  },
876
942
  ctx,
877
943
  ),
944
+ ctx,
878
945
  );
879
946
 
880
947
  expect(result.exchanges).toEqual([
@@ -888,22 +955,24 @@ describe("exchange_delivered_order_items", () => {
888
955
  5,
889
956
  );
890
957
 
891
- // The stored fields keep tau2's shape: two sorted SETS, and the second one
892
- // really is in the other order.
958
+ // Positionally aligned with each other AND with `result.exchanges`.
893
959
  const order = retailSlot.get(ctx).store.orders["#W5866402"];
894
960
  expect(order?.exchange_items).toEqual(["6242772310", "9727387530"]);
895
- expect(order?.exchange_new_items).toEqual(["2509076505", "7407838442"]);
961
+ expect(order?.exchange_new_items).toEqual(["7407838442", "2509076505"]);
896
962
  });
897
963
 
898
964
  test("does NOT move the gift-card balance — it is a request, not a settlement", async () => {
899
965
  const ctx = await authedCtx("aarav.anderson9752@example.com");
900
- await exchangeDeliveredOrderItems.execute(
901
- {
902
- order_id: "#W4316152",
903
- item_ids: ["7292993796"],
904
- new_item_ids: ["3909406921"],
905
- payment_method_id: "gift_card_7245904",
906
- },
966
+ await confirmed(
967
+ await exchangeDeliveredOrderItems.execute(
968
+ {
969
+ order_id: "#W4316152",
970
+ item_ids: ["7292993796"],
971
+ new_item_ids: ["3909406921"],
972
+ payment_method_id: "gift_card_7245904",
973
+ },
974
+ ctx,
975
+ ),
907
976
  ctx,
908
977
  );
909
978
  const card =
@@ -913,13 +982,16 @@ describe("exchange_delivered_order_items", () => {
913
982
 
914
983
  test("leaves the order's items unchanged until the exchange is fulfilled", async () => {
915
984
  const ctx = await authedCtx("aarav.anderson9752@example.com");
916
- await exchangeDeliveredOrderItems.execute(
917
- {
918
- order_id: "#W4316152",
919
- item_ids: ["7292993796"],
920
- new_item_ids: ["3909406921"],
921
- payment_method_id: "gift_card_7245904",
922
- },
985
+ await confirmed(
986
+ await exchangeDeliveredOrderItems.execute(
987
+ {
988
+ order_id: "#W4316152",
989
+ item_ids: ["7292993796"],
990
+ new_item_ids: ["3909406921"],
991
+ payment_method_id: "gift_card_7245904",
992
+ },
993
+ ctx,
994
+ ),
923
995
  ctx,
924
996
  );
925
997
  expect(retailSlot.get(ctx).store.orders["#W4316152"]?.items.map((i) => i.item_id)).toEqual([
@@ -930,7 +1002,7 @@ describe("exchange_delivered_order_items", () => {
930
1002
 
931
1003
  test("exchanges both copies of a duplicate and doubles the difference", async () => {
932
1004
  const ctx = await authedCtx("aarav.anderson9752@example.com");
933
- const result = ok<ExchangeResult>(
1005
+ const result = await confirmed<ExchangeResult>(
934
1006
  await exchangeDeliveredOrderItems.execute(
935
1007
  {
936
1008
  order_id: "#W4316152",
@@ -940,13 +1012,14 @@ describe("exchange_delivered_order_items", () => {
940
1012
  },
941
1013
  ctx,
942
1014
  ),
1015
+ ctx,
943
1016
  );
944
1017
  expect(result.price_difference).toBe(6.9);
945
1018
  });
946
1019
 
947
1020
  test("records a negative difference as a refund direction", async () => {
948
1021
  const ctx = await authedCtx("olivia.ito5204@example.com");
949
- const result = ok<ExchangeResult>(
1022
+ const result = await confirmed<ExchangeResult>(
950
1023
  await exchangeDeliveredOrderItems.execute(
951
1024
  {
952
1025
  order_id: "#W5866402",
@@ -956,6 +1029,7 @@ describe("exchange_delivered_order_items", () => {
956
1029
  },
957
1030
  ctx,
958
1031
  ),
1032
+ ctx,
959
1033
  );
960
1034
  expect(result.price_difference).toBe(-40.86);
961
1035
  });
@@ -1016,13 +1090,16 @@ describe("exchange_delivered_order_items", () => {
1016
1090
  );
1017
1091
  expect(isToolFailure(pending)).toBe(true);
1018
1092
 
1019
- await exchangeDeliveredOrderItems.execute(
1020
- {
1021
- order_id: "#W4316152",
1022
- item_ids: ["7292993796"],
1023
- new_item_ids: ["3909406921"],
1024
- payment_method_id: "gift_card_7245904",
1025
- },
1093
+ await confirmed(
1094
+ await exchangeDeliveredOrderItems.execute(
1095
+ {
1096
+ order_id: "#W4316152",
1097
+ item_ids: ["7292993796"],
1098
+ new_item_ids: ["3909406921"],
1099
+ payment_method_id: "gift_card_7245904",
1100
+ },
1101
+ ctx,
1102
+ ),
1026
1103
  ctx,
1027
1104
  );
1028
1105
  const again = await exchangeDeliveredOrderItems.execute(
@@ -1039,12 +1116,15 @@ describe("exchange_delivered_order_items", () => {
1039
1116
 
1040
1117
  test("a returned order can no longer be exchanged", async () => {
1041
1118
  const ctx = await authedCtx("aarav.anderson9752@example.com");
1042
- await returnDeliveredOrderItems.execute(
1043
- {
1044
- order_id: "#W4316152",
1045
- item_ids: ["7292993796"],
1046
- payment_method_id: "gift_card_7245904",
1047
- },
1119
+ await confirmed(
1120
+ await returnDeliveredOrderItems.execute(
1121
+ {
1122
+ order_id: "#W4316152",
1123
+ item_ids: ["7292993796"],
1124
+ payment_method_id: "gift_card_7245904",
1125
+ },
1126
+ ctx,
1127
+ ),
1048
1128
  ctx,
1049
1129
  );
1050
1130
  const result = await exchangeDeliveredOrderItems.execute(
@@ -1,6 +1,6 @@
1
1
  import { agent } from "@alexkroman1/aai";
2
2
  import { storeView } from "./shared.ts";
3
- import { retailSlot } from "./store.ts";
3
+ import { callFlow, gateFor, record, retailSlot } from "./store.ts";
4
4
 
5
5
  export default agent({
6
6
  name: "Retail Support",
@@ -26,4 +26,38 @@ export default agent({
26
26
  greeting:
27
27
  "Thanks for calling. Before I can look anything up I'll need to find your account — " +
28
28
  "what's the email address on it?",
29
+
30
+ /**
31
+ * The activity feed's blocked lines, which no tool wrapper could write.
32
+ *
33
+ * `retailTool` records a line per call from INSIDE the body, so it sees every
34
+ * call the caller's own logic answered — and none that `callFlow` refused,
35
+ * because a gated refusal short-circuits before the body. The sidebar simply
36
+ * stopped showing the most interesting calls the model makes: the ones it
37
+ * tried too early.
38
+ *
39
+ * `tool.called` is emitted by the runtime for every call the model makes, so a
40
+ * hook is the only place that observation is available at all. It is also the
41
+ * shape of hook this template is here to show: it WRITES the session's state
42
+ * and it cannot speak — nothing here changes what the agent says, or stops the
43
+ * refusal still reaching the model. It does neither.
44
+ *
45
+ * The gate is evaluated a second time rather than reported by the first,
46
+ * because the refusal happens later and inside the body. That is one fact read
47
+ * twice from one source (`gateFor`, which `retailTool` fills), not two copies
48
+ * of it — and the only cost of the two evaluations disagreeing is a duplicate
49
+ * or missing sidebar line, never a wrong store.
50
+ */
51
+ events: {
52
+ "tool.called": (event, ctx) => {
53
+ const when = gateFor(event.toolName);
54
+ // Not a retail tool, or one this call may run: either way the wrapper owns
55
+ // the line, and writing one here would double it.
56
+ if (!when || when.some((state) => callFlow.matches(ctx, state))) return;
57
+ const at = callFlow.position(ctx);
58
+ retailSlot.update(ctx, (state) => {
59
+ record(state, event.toolName, `blocked: call is at ${at.state}`);
60
+ });
61
+ },
62
+ },
29
63
  });