@alexkroman1/aai-cli 6.11.0 → 8.0.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 (234) hide show
  1. package/README.md +1 -0
  2. package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
  3. package/dist/{_bundler-C2BDwU7r.mjs → _bundler-CDuYl5Gb.mjs} +2 -2
  4. package/dist/_dev-env.d.ts +22 -2
  5. package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-Me06wgeF.mjs} +38 -47
  6. package/dist/_dev-server.d.ts +0 -17
  7. package/dist/{_init-C57bWAQ8.mjs → _init-BhEfQ6Yi.mjs} +3 -2
  8. package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
  9. package/dist/_preflight.d.ts +1 -1
  10. package/dist/_resource-commands.d.ts +23 -0
  11. package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-z-USxPtL.mjs} +16 -12
  12. package/dist/_slug-api.d.ts +5 -2
  13. package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
  14. package/dist/{_templates-Dxb_P2Wz.mjs → _templates-4WcKOjS5.mjs} +5 -6
  15. package/dist/_templates.d.ts +3 -4
  16. package/dist/_workflow-scan.d.ts +108 -0
  17. package/dist/{build-Dfza2pRI.mjs → build-BBxf8vom.mjs} +6 -3
  18. package/dist/cli.mjs +346 -349
  19. package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
  20. package/dist/client-bundler.mjs +1 -1
  21. package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
  22. package/dist/{deploy-B8lubiRT.mjs → deploy-DRC8jWr8.mjs} +5 -5
  23. package/dist/{dev-Uq5ujP8a.mjs → dev-BvcctCn2.mjs} +1 -1
  24. package/dist/eval-DfQ8cm5p.mjs +56 -0
  25. package/dist/eval.d.ts +43 -0
  26. package/dist/{init-l6xfU4xX.mjs → init-CUo87gcI.mjs} +3 -3
  27. package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
  28. package/dist/{logs-BWVGSjLR.mjs → logs-CQ2T46dr.mjs} +2 -2
  29. package/dist/project-config.mjs +1 -1
  30. package/dist/scaffold/CLAUDE.md +442 -80
  31. package/dist/scaffold/package.json +6 -3
  32. package/dist/scaffold/server.mjs +11 -4
  33. package/dist/scaffold/tsconfig.json +0 -1
  34. package/dist/{secret-BuMuFR4B.mjs → secret-Ct34kdgU.mjs} +2 -2
  35. package/dist/{storage-DzRZ-eCw.mjs → storage-i_79ZwAR.mjs} +2 -2
  36. package/dist/{studio-BCNUpDgP.mjs → studio-CzTzPqe1.mjs} +4 -4
  37. package/dist/templates/briefing-desk/agent.test.ts +350 -0
  38. package/dist/templates/briefing-desk/agent.ts +30 -0
  39. package/dist/templates/briefing-desk/shared.ts +200 -0
  40. package/dist/templates/briefing-desk/system-prompt.md +39 -0
  41. package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
  42. package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
  43. package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
  44. package/dist/templates/call-audit/agent.eval.test.ts +474 -0
  45. package/dist/templates/call-audit/agent.test.ts +57 -204
  46. package/dist/templates/call-audit/agent.ts +32 -19
  47. package/dist/templates/call-audit/client.tsx +17 -54
  48. package/dist/templates/call-audit/workflows/audit.ts +11 -18
  49. package/dist/templates/call-audit/workflows/ingest.ts +106 -114
  50. package/dist/templates/call-audit/workflows/media.ts +2 -12
  51. package/dist/templates/call-audit/workflows/summarize.ts +47 -52
  52. package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
  53. package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
  54. package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
  55. package/dist/templates/dispatch-center/agent.test.ts +149 -25
  56. package/dist/templates/dispatch-center/client.tsx +239 -129
  57. package/dist/templates/dispatch-center/shared.ts +99 -1
  58. package/dist/templates/dispatch-center/system-prompt.md +3 -1
  59. package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
  60. package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
  61. package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
  62. package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
  63. package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
  64. package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
  65. package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
  66. package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
  67. package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
  68. package/dist/templates/embedded-assets/agent.test.ts +17 -14
  69. package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
  70. package/dist/templates/health-assistant/agent.test.ts +22 -4
  71. package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
  72. package/dist/templates/infocom-adventure/agent.test.ts +53 -24
  73. package/dist/templates/infocom-adventure/agent.ts +21 -0
  74. package/dist/templates/infocom-adventure/client.tsx +246 -164
  75. package/dist/templates/infocom-adventure/shared.ts +20 -9
  76. package/dist/templates/infocom-adventure/system-prompt.md +1 -1
  77. package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
  78. package/dist/templates/link-digest/agent.eval.test.ts +195 -0
  79. package/dist/templates/link-digest/agent.test.ts +24 -19
  80. package/dist/templates/link-digest/client.tsx +47 -61
  81. package/dist/templates/link-digest/workflows/digest.ts +19 -31
  82. package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
  83. package/dist/templates/night-owl/agent.eval.test.ts +272 -0
  84. package/dist/templates/night-owl/agent.test.ts +70 -19
  85. package/dist/templates/night-owl/agent.ts +5 -0
  86. package/dist/templates/night-owl/client.tsx +56 -56
  87. package/dist/templates/night-owl/shared.ts +24 -0
  88. package/dist/templates/night-owl/tools/recommend.ts +18 -6
  89. package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
  90. package/dist/templates/personal-finance/system-prompt.md +4 -0
  91. package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
  92. package/dist/templates/pipeline-simple/agent.ts +2 -2
  93. package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
  94. package/dist/templates/pizza-ordering/agent.test.ts +42 -27
  95. package/dist/templates/pizza-ordering/client.tsx +9 -26
  96. package/dist/templates/pizza-ordering/shared.ts +20 -1
  97. package/dist/templates/pizza-ordering/system-prompt.md +6 -0
  98. package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
  99. package/dist/templates/plan-and-execute/agent.test.ts +99 -58
  100. package/dist/templates/plan-and-execute/agent.ts +1 -1
  101. package/dist/templates/plan-and-execute/client.tsx +12 -15
  102. package/dist/templates/plan-and-execute/shared.ts +71 -2
  103. package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
  104. package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
  105. package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
  106. package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
  107. package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
  108. package/dist/templates/podcast-digest/agent.test.ts +717 -0
  109. package/dist/templates/podcast-digest/agent.ts +139 -0
  110. package/dist/templates/podcast-digest/client.tsx +154 -0
  111. package/dist/templates/podcast-digest/workflows/digest.ts +444 -0
  112. package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
  113. package/dist/templates/podcast-digest/workflows/slack.ts +88 -0
  114. package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
  115. package/dist/templates/recap-workflow/agent.test.ts +97 -94
  116. package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
  117. package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
  118. package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
  119. package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
  120. package/dist/templates/redline/agent.eval.test.ts +209 -0
  121. package/dist/templates/redline/agent.test.ts +17 -15
  122. package/dist/templates/redline/client.tsx +12 -12
  123. package/dist/templates/redline/workflows/redline.ts +19 -31
  124. package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
  125. package/dist/templates/research-workflow/agent.test.ts +60 -59
  126. package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
  127. package/dist/templates/research-workflow/tools/research_status.ts +9 -11
  128. package/dist/templates/research-workflow/workflows/research.ts +44 -61
  129. package/dist/templates/retail/address.ts +87 -1
  130. package/dist/templates/retail/agent.eval.test.ts +314 -0
  131. package/dist/templates/retail/agent.test.ts +195 -112
  132. package/dist/templates/retail/agent.ts +35 -1
  133. package/dist/templates/retail/cancel.ts +116 -0
  134. package/dist/templates/retail/client.tsx +265 -117
  135. package/dist/templates/retail/payment.ts +107 -0
  136. package/dist/templates/retail/pending.test.ts +196 -0
  137. package/dist/templates/retail/pending.ts +137 -0
  138. package/dist/templates/retail/registry.test.ts +150 -7
  139. package/dist/templates/retail/returns.ts +102 -0
  140. package/dist/templates/retail/shared.test.ts +42 -0
  141. package/dist/templates/retail/shared.ts +35 -0
  142. package/dist/templates/retail/store.test.ts +84 -15
  143. package/dist/templates/retail/store.ts +231 -48
  144. package/dist/templates/retail/swap.test.ts +4 -4
  145. package/dist/templates/retail/swap.ts +248 -15
  146. package/dist/templates/retail/system-prompt.md +69 -28
  147. package/dist/templates/retail/tools/cancel_change.ts +33 -0
  148. package/dist/templates/retail/tools/cancel_pending_order.ts +16 -60
  149. package/dist/templates/retail/tools/confirm_change.ts +42 -0
  150. package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -67
  151. package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
  152. package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
  153. package/dist/templates/retail/tools/get_item_details.ts +3 -4
  154. package/dist/templates/retail/tools/get_order_details.ts +3 -4
  155. package/dist/templates/retail/tools/get_product_details.ts +3 -4
  156. package/dist/templates/retail/tools/get_user_details.ts +2 -2
  157. package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
  158. package/dist/templates/retail/tools/modify_pending_order_address.ts +13 -25
  159. package/dist/templates/retail/tools/modify_pending_order_items.ts +23 -62
  160. package/dist/templates/retail/tools/modify_pending_order_payment.ts +16 -78
  161. package/dist/templates/retail/tools/modify_user_address.ts +13 -16
  162. package/dist/templates/retail/tools/return_delivered_order_items.ts +17 -72
  163. package/dist/templates/retail/tools/transfer_to_human_agents.ts +12 -6
  164. package/dist/templates/simple/agent.eval.test.ts +54 -0
  165. package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
  166. package/dist/templates/solo-rpg/agent.test.ts +227 -48
  167. package/dist/templates/solo-rpg/client.tsx +38 -37
  168. package/dist/templates/solo-rpg/shared.ts +145 -19
  169. package/dist/templates/solo-rpg/system-prompt.md +3 -2
  170. package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
  171. package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
  172. package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
  173. package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
  174. package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
  175. package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
  176. package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
  177. package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
  178. package/dist/templates/spoken-summary/agent.test.ts +68 -97
  179. package/dist/templates/spoken-summary/agent.ts +30 -17
  180. package/dist/templates/spoken-summary/client.tsx +10 -50
  181. package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
  182. package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
  183. package/dist/templates/support-line/agent.eval.test.ts +273 -0
  184. package/dist/templates/support-line/agent.test.ts +11 -16
  185. package/dist/templates/support-line/agent.ts +1 -1
  186. package/dist/templates/support-line/client.tsx +9 -9
  187. package/dist/templates/support-line/nodes.ts +100 -0
  188. package/dist/templates/support-line/procedure.ts +407 -0
  189. package/dist/templates/support-line/system-prompt.md +2 -1
  190. package/dist/templates/support-line/tools/answer_question.ts +66 -4
  191. package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
  192. package/dist/templates/transcription-workflow/agent.test.ts +94 -194
  193. package/dist/templates/transcription-workflow/agent.ts +1 -1
  194. package/dist/templates/transcription-workflow/client.tsx +17 -42
  195. package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
  196. package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
  197. package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
  198. package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
  199. package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
  200. package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
  201. package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
  202. package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
  203. package/dist/templates/travel-concierge/agent.test.ts +174 -43
  204. package/dist/templates/travel-concierge/agent.ts +18 -9
  205. package/dist/templates/travel-concierge/client.tsx +11 -23
  206. package/dist/templates/travel-concierge/routing.ts +34 -15
  207. package/dist/templates/travel-concierge/shared.ts +180 -12
  208. package/dist/templates/travel-concierge/system-prompt.md +7 -1
  209. package/dist/templates/travel-concierge/tools/book_car_rental.ts +8 -5
  210. package/dist/templates/travel-concierge/tools/book_excursion.ts +8 -5
  211. package/dist/templates/travel-concierge/tools/book_hotel.ts +8 -5
  212. package/dist/templates/travel-concierge/tools/cancel_ticket.ts +8 -5
  213. package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
  214. package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
  215. package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
  216. package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
  217. package/dist/templates/travel-concierge/tools/update_ticket.ts +8 -5
  218. package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
  219. package/dist/templates/web-researcher/system-prompt.md +15 -4
  220. package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
  221. package/dist/test.d.ts +29 -5
  222. package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-CCVEDjm1.mjs} +241 -3
  223. package/dist/worker-bundler.mjs +1 -1
  224. package/dist/{workflow-CFpxOFfQ.mjs → workflow-BflATMmU.mjs} +47 -12
  225. package/dist/workflow-bundler.d.ts +6 -1
  226. package/dist/workflow.d.ts +1 -1
  227. package/package.json +5 -4
  228. package/dist/eject-31gjtaHF.mjs +0 -116
  229. package/dist/eject.d.ts +0 -60
  230. package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
  231. package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
  232. package/dist/templates/support-line/graph.ts +0 -224
  233. package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
  234. /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
@@ -1,11 +1,13 @@
1
1
  import type { ToolContext } from "@alexkroman1/aai";
2
2
  import { isToolFailure } from "@alexkroman1/aai";
3
- import { createToolContext } from "@alexkroman1/aai/testing";
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
- import { retailSlot } from "./store.ts";
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";
@@ -27,27 +29,6 @@ function makeCtx(): ToolContext {
27
29
  return createToolContext();
28
30
  }
29
31
 
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;
49
- }
50
-
51
32
  /** A context already authenticated as `userId`, via the real tool. */
52
33
  async function authedCtx(email: string): Promise<ToolContext> {
53
34
  const ctx = makeCtx();
@@ -55,6 +36,26 @@ async function authedCtx(email: string): Promise<ToolContext> {
55
36
  return ctx;
56
37
  }
57
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
+
58
59
  describe("authentication", () => {
59
60
  test("find_user_id_by_email is case-insensitive and authenticates the session", async () => {
60
61
  const ctx = makeCtx();
@@ -63,6 +64,9 @@ describe("authentication", () => {
63
64
  );
64
65
  expect(result.user_id).toBe("olivia_ito_3591");
65
66
  expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
67
+ // Two facts, two homes: the store latches WHO, the flow holds WHETHER.
68
+ // `serving` is a parent now, so an identified call reports its child.
69
+ expect(callFlow.position(ctx).state).toBe("serving.helping");
66
70
  });
67
71
 
68
72
  test("an unknown email is refused and leaves the session unauthenticated", async () => {
@@ -70,6 +74,9 @@ describe("authentication", () => {
70
74
  const result = await findUserIdByEmail.execute({ email: "nobody@example.com" }, ctx);
71
75
  expect(isToolFailure(result)).toBe(true);
72
76
  expect(retailSlot.get(ctx).authenticatedUserId).toBeNull();
77
+ // `IDENTIFIED` is not sent when the body answers a `ToolFailure`, so a
78
+ // lookup that found nobody cannot leave the call one step ahead of itself.
79
+ expect(callFlow.position(ctx).state).toBe("identifying");
73
80
  });
74
81
 
75
82
  test("find_user_id_by_name_zip is case-insensitive on names and exact on zip", async () => {
@@ -121,6 +128,9 @@ describe("authentication", () => {
121
128
  );
122
129
  expect(isToolFailure(switched) && switched.error.toLowerCase()).toContain("one customer");
123
130
  expect(retailSlot.get(ctx).authenticatedUserId).toBe("olivia_ito_3591");
131
+ // The tool is legal in `serving` — the refusal comes from `authenticateAs`,
132
+ // not from the gate — so the call stays exactly where it was.
133
+ expect(callFlow.position(ctx).state).toBe("serving.helping");
124
134
  });
125
135
 
126
136
  test("switching via name + zip is refused too — both doors, one lock", async () => {
@@ -237,11 +247,12 @@ interface CancelOrderResult {
237
247
  describe("cancel_pending_order", () => {
238
248
  test("cancels a pending order and credits a gift card immediately", async () => {
239
249
  const ctx = await authedCtx("aarav.anderson9752@example.com");
240
- const result = ok<CancelOrderResult>(
250
+ const result = await confirmed<CancelOrderResult>(
241
251
  await cancelPendingOrder.execute(
242
252
  { order_id: "#W9300146", reason: "ordered by mistake" },
243
253
  ctx,
244
254
  ),
255
+ ctx,
245
256
  );
246
257
  expect(result.status).toBe("cancelled");
247
258
  expect(result.refund_immediate).toBe(true);
@@ -256,7 +267,10 @@ describe("cancel_pending_order", () => {
256
267
 
257
268
  test("appends a matching refund to the payment history", async () => {
258
269
  const ctx = await authedCtx("aarav.anderson9752@example.com");
259
- 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
+ );
260
274
  const history = retailSlot.get(ctx).store.orders["#W9300146"]?.payment_history ?? [];
261
275
  expect(history).toHaveLength(2);
262
276
  expect(history[1]).toMatchObject({
@@ -268,8 +282,9 @@ describe("cancel_pending_order", () => {
268
282
 
269
283
  test("a non-gift-card refund takes 5-7 days and moves no balance", async () => {
270
284
  const ctx = await authedCtx("olivia.ito5204@example.com");
271
- const result = ok<CancelOrderResult>(
285
+ const result = await confirmed<CancelOrderResult>(
272
286
  await cancelPendingOrder.execute({ order_id: "#W5442520", reason: "no longer needed" }, ctx),
287
+ ctx,
273
288
  );
274
289
  expect(result.refund_immediate).toBe(false);
275
290
  expect(result.message).toContain("5 to 7");
@@ -311,7 +326,10 @@ describe("cancel_pending_order", () => {
311
326
 
312
327
  test("refuses cancelling twice", async () => {
313
328
  const ctx = await authedCtx("aarav.anderson9752@example.com");
314
- 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
+ );
315
333
  const second = await cancelPendingOrder.execute(
316
334
  { order_id: "#W9300146", reason: "no longer needed" },
317
335
  ctx,
@@ -324,11 +342,12 @@ describe("cancel_pending_order", () => {
324
342
 
325
343
  test("resolves spoken shorthand to the single pending order", async () => {
326
344
  const ctx = await authedCtx("aarav.anderson9752@example.com");
327
- const result = ok<CancelOrderResult>(
345
+ const result = await confirmed<CancelOrderResult>(
328
346
  await cancelPendingOrder.execute(
329
347
  { order_id: "my pending order", reason: "ordered by mistake" },
330
348
  ctx,
331
349
  ),
350
+ ctx,
332
351
  );
333
352
  expect(result.order_id).toBe("#W9300146");
334
353
  });
@@ -364,8 +383,9 @@ interface AddressToolResult {
364
383
  describe("modify_pending_order_address", () => {
365
384
  test("rewrites a pending order's shipping address", async () => {
366
385
  const ctx = await authedCtx("aarav.anderson9752@example.com");
367
- const result = ok<AddressToolResult>(
386
+ const result = await confirmed<AddressToolResult>(
368
387
  await modifyPendingOrderAddress.execute({ order_id: "#W9300146", ...NEW_ADDRESS }, ctx),
388
+ ctx,
369
389
  );
370
390
  expect(result.address).toEqual(NEW_ADDRESS);
371
391
  expect(retailSlot.get(ctx).store.orders["#W9300146"]?.address).toEqual(NEW_ADDRESS);
@@ -373,7 +393,10 @@ describe("modify_pending_order_address", () => {
373
393
 
374
394
  test("leaves the customer's default address untouched", async () => {
375
395
  const ctx = await authedCtx("aarav.anderson9752@example.com");
376
- 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
+ );
377
400
  expect(retailSlot.get(ctx).store.users.aarav_anderson_8794?.address.zip).toBe("19031");
378
401
  });
379
402
 
@@ -413,14 +436,20 @@ describe("modify_pending_order_address", () => {
413
436
  describe("modify_user_address", () => {
414
437
  test("rewrites the customer's default address", async () => {
415
438
  const ctx = await authedCtx("emma.smith3991@example.com");
416
- 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
+ );
417
443
  expect(retailSlot.get(ctx).store.users.emma_smith_8564?.address).toEqual(NEW_ADDRESS);
418
444
  });
419
445
 
420
446
  test("leaves existing orders' addresses untouched", async () => {
421
447
  const ctx = await authedCtx("emma.smith3991@example.com");
422
448
  const before = structuredClone(retailSlot.get(ctx).store.orders["#W2417020"]?.address);
423
- 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
+ );
424
453
  expect(retailSlot.get(ctx).store.orders["#W2417020"]?.address).toEqual(before);
425
454
  });
426
455
 
@@ -454,7 +483,7 @@ interface ModifyItemsResult {
454
483
  describe("modify_pending_order_items", () => {
455
484
  test("swaps an item, charges the difference to a gift card, and goes terminal", async () => {
456
485
  const ctx = await authedCtx("emma.smith3991@example.com");
457
- const result = ok<ModifyItemsResult>(
486
+ const result = await confirmed<ModifyItemsResult>(
458
487
  await modifyPendingOrderItems.execute(
459
488
  {
460
489
  order_id: "#W2417020",
@@ -464,6 +493,7 @@ describe("modify_pending_order_items", () => {
464
493
  },
465
494
  ctx,
466
495
  ),
496
+ ctx,
467
497
  );
468
498
  expect(result.price_difference).toBe(-382.03);
469
499
  expect(result.status).toBe("pending (item modified)");
@@ -479,13 +509,16 @@ describe("modify_pending_order_items", () => {
479
509
 
480
510
  test("records the difference as a refund entry when the new item is cheaper", async () => {
481
511
  const ctx = await authedCtx("emma.smith3991@example.com");
482
- await modifyPendingOrderItems.execute(
483
- {
484
- order_id: "#W2417020",
485
- item_ids: ["8997785118"],
486
- new_item_ids: ["6017636844"],
487
- payment_method_id: "gift_card_8541487",
488
- },
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
+ ),
489
522
  ctx,
490
523
  );
491
524
  const history = retailSlot.get(ctx).store.orders["#W2417020"]?.payment_history ?? [];
@@ -498,13 +531,16 @@ describe("modify_pending_order_items", () => {
498
531
 
499
532
  test("refuses a second modification — the status is terminal", async () => {
500
533
  const ctx = await authedCtx("emma.smith3991@example.com");
501
- await modifyPendingOrderItems.execute(
502
- {
503
- order_id: "#W2417020",
504
- item_ids: ["8997785118"],
505
- new_item_ids: ["6017636844"],
506
- payment_method_id: "gift_card_8541487",
507
- },
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
+ ),
508
544
  ctx,
509
545
  );
510
546
  const second = await modifyPendingOrderItems.execute(
@@ -521,13 +557,16 @@ describe("modify_pending_order_items", () => {
521
557
 
522
558
  test("a modified order can no longer be cancelled", async () => {
523
559
  const ctx = await authedCtx("emma.smith3991@example.com");
524
- await modifyPendingOrderItems.execute(
525
- {
526
- order_id: "#W2417020",
527
- item_ids: ["8997785118"],
528
- new_item_ids: ["6017636844"],
529
- payment_method_id: "gift_card_8541487",
530
- },
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
+ ),
531
570
  ctx,
532
571
  );
533
572
  const cancelled = await cancelPendingOrder.execute(
@@ -565,7 +604,7 @@ describe("modify_pending_order_items", () => {
565
604
 
566
605
  test("the same upgrade succeeds on a payment method with no balance to run out of", async () => {
567
606
  const ctx = await authedCtx("anya.garcia2061@example.com");
568
- const result = ok<ModifyItemsResult>(
607
+ const result = await confirmed<ModifyItemsResult>(
569
608
  await modifyPendingOrderItems.execute(
570
609
  {
571
610
  order_id: "#W6436609",
@@ -575,6 +614,7 @@ describe("modify_pending_order_items", () => {
575
614
  },
576
615
  ctx,
577
616
  ),
617
+ ctx,
578
618
  );
579
619
  expect(result.price_difference).toBe(167.37);
580
620
  const history = retailSlot.get(ctx).store.orders["#W6436609"]?.payment_history ?? [];
@@ -628,11 +668,12 @@ describe("modify_pending_order_payment", () => {
628
668
  test("moves an order to a different method, refunding the old one", async () => {
629
669
  const ctx = await authedCtx("olivia.ito5204@example.com");
630
670
  // #W5442520 is $663.85 on credit_card_9753331. Pay by PayPal instead.
631
- const result = ok<ModifyPaymentResult>(
671
+ const result = await confirmed<ModifyPaymentResult>(
632
672
  await modifyPendingOrderPayment.execute(
633
673
  { order_id: "#W5442520", payment_method_id: "paypal_8049766" },
634
674
  ctx,
635
675
  ),
676
+ ctx,
636
677
  );
637
678
  expect(result.status).toBe("pending");
638
679
 
@@ -653,11 +694,13 @@ describe("modify_pending_order_payment", () => {
653
694
  test("refunds a gift card that was the original method", async () => {
654
695
  const ctx = await authedCtx("aarav.gonzalez9269@example.com");
655
696
  // #W9160732 is $1011.54 on gift_card_5979071 ($96.00). Move to PayPal.
656
- const result = await modifyPendingOrderPayment.execute(
657
- { 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
+ ),
658
702
  ctx,
659
703
  );
660
- expect(isToolFailure(result)).toBe(false);
661
704
  const card =
662
705
  retailSlot.get(ctx).store.users.aarav_gonzalez_5113?.payment_methods.gift_card_5979071;
663
706
  expect(card?.source === "gift_card" && card.balance).toBe(1107.54);
@@ -744,7 +787,7 @@ interface ExchangeResult {
744
787
  describe("return_delivered_order_items", () => {
745
788
  test("requests a return refunded to the original method", async () => {
746
789
  const ctx = await authedCtx("harper.brown3965@example.com");
747
- const result = ok<ReturnResult>(
790
+ const result = await confirmed<ReturnResult>(
748
791
  await returnDeliveredOrderItems.execute(
749
792
  {
750
793
  order_id: "#W1840144",
@@ -753,6 +796,7 @@ describe("return_delivered_order_items", () => {
753
796
  },
754
797
  ctx,
755
798
  ),
799
+ ctx,
756
800
  );
757
801
  expect(result.status).toBe("return requested");
758
802
  const order = retailSlot.get(ctx).store.orders["#W1840144"];
@@ -789,20 +833,25 @@ describe("return_delivered_order_items", () => {
789
833
  expect(retailSlot.get(ctx).store.orders["#W1840144"]?.status).toBe("delivered");
790
834
  });
791
835
 
792
- 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 () => {
793
837
  const ctx = await authedCtx("harper.brown3965@example.com");
794
- const result = await returnDeliveredOrderItems.execute(
795
- {
796
- order_id: "#W1840144",
797
- item_ids: ["8590708195", "6534134392", "8590708195"],
798
- payment_method_id: "paypal_2306935",
799
- },
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
+ ),
800
847
  ctx,
801
848
  );
802
- 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.
803
852
  expect(retailSlot.get(ctx).store.orders["#W1840144"]?.return_items).toEqual([
804
- "6534134392",
805
853
  "8590708195",
854
+ "6534134392",
806
855
  "8590708195",
807
856
  ]);
808
857
  });
@@ -832,8 +881,11 @@ describe("return_delivered_order_items", () => {
832
881
  );
833
882
  expect(isToolFailure(pending)).toBe(true);
834
883
 
835
- await returnDeliveredOrderItems.execute(
836
- { 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
+ ),
837
889
  ctx,
838
890
  );
839
891
  const again = await returnDeliveredOrderItems.execute(
@@ -847,7 +899,7 @@ describe("return_delivered_order_items", () => {
847
899
  describe("exchange_delivered_order_items", () => {
848
900
  test("records an exchange with a positive price difference", async () => {
849
901
  const ctx = await authedCtx("aarav.anderson9752@example.com");
850
- const result = ok<ExchangeResult>(
902
+ const result = await confirmed<ExchangeResult>(
851
903
  await exchangeDeliveredOrderItems.execute(
852
904
  {
853
905
  order_id: "#W4316152",
@@ -857,6 +909,7 @@ describe("exchange_delivered_order_items", () => {
857
909
  },
858
910
  ctx,
859
911
  ),
912
+ ctx,
860
913
  );
861
914
  expect(result.price_difference).toBe(3.45);
862
915
  expect(result.exchanges).toEqual([
@@ -869,16 +922,16 @@ describe("exchange_delivered_order_items", () => {
869
922
  expect(order?.exchange_price_difference).toBe(3.45);
870
923
  });
871
924
 
872
- 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 () => {
873
926
  // `#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.
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.
880
933
  const ctx = await authedCtx("olivia.ito5204@example.com");
881
- const result = ok<ExchangeResult>(
934
+ const result = await confirmed<ExchangeResult>(
882
935
  await exchangeDeliveredOrderItems.execute(
883
936
  {
884
937
  order_id: "#W5866402",
@@ -888,6 +941,7 @@ describe("exchange_delivered_order_items", () => {
888
941
  },
889
942
  ctx,
890
943
  ),
944
+ ctx,
891
945
  );
892
946
 
893
947
  expect(result.exchanges).toEqual([
@@ -901,22 +955,24 @@ describe("exchange_delivered_order_items", () => {
901
955
  5,
902
956
  );
903
957
 
904
- // The stored fields keep tau2's shape: two sorted SETS, and the second one
905
- // really is in the other order.
958
+ // Positionally aligned with each other AND with `result.exchanges`.
906
959
  const order = retailSlot.get(ctx).store.orders["#W5866402"];
907
960
  expect(order?.exchange_items).toEqual(["6242772310", "9727387530"]);
908
- expect(order?.exchange_new_items).toEqual(["2509076505", "7407838442"]);
961
+ expect(order?.exchange_new_items).toEqual(["7407838442", "2509076505"]);
909
962
  });
910
963
 
911
964
  test("does NOT move the gift-card balance — it is a request, not a settlement", async () => {
912
965
  const ctx = await authedCtx("aarav.anderson9752@example.com");
913
- await exchangeDeliveredOrderItems.execute(
914
- {
915
- order_id: "#W4316152",
916
- item_ids: ["7292993796"],
917
- new_item_ids: ["3909406921"],
918
- payment_method_id: "gift_card_7245904",
919
- },
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
+ ),
920
976
  ctx,
921
977
  );
922
978
  const card =
@@ -926,13 +982,16 @@ describe("exchange_delivered_order_items", () => {
926
982
 
927
983
  test("leaves the order's items unchanged until the exchange is fulfilled", async () => {
928
984
  const ctx = await authedCtx("aarav.anderson9752@example.com");
929
- await exchangeDeliveredOrderItems.execute(
930
- {
931
- order_id: "#W4316152",
932
- item_ids: ["7292993796"],
933
- new_item_ids: ["3909406921"],
934
- payment_method_id: "gift_card_7245904",
935
- },
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
+ ),
936
995
  ctx,
937
996
  );
938
997
  expect(retailSlot.get(ctx).store.orders["#W4316152"]?.items.map((i) => i.item_id)).toEqual([
@@ -943,7 +1002,7 @@ describe("exchange_delivered_order_items", () => {
943
1002
 
944
1003
  test("exchanges both copies of a duplicate and doubles the difference", async () => {
945
1004
  const ctx = await authedCtx("aarav.anderson9752@example.com");
946
- const result = ok<ExchangeResult>(
1005
+ const result = await confirmed<ExchangeResult>(
947
1006
  await exchangeDeliveredOrderItems.execute(
948
1007
  {
949
1008
  order_id: "#W4316152",
@@ -953,13 +1012,14 @@ describe("exchange_delivered_order_items", () => {
953
1012
  },
954
1013
  ctx,
955
1014
  ),
1015
+ ctx,
956
1016
  );
957
1017
  expect(result.price_difference).toBe(6.9);
958
1018
  });
959
1019
 
960
1020
  test("records a negative difference as a refund direction", async () => {
961
1021
  const ctx = await authedCtx("olivia.ito5204@example.com");
962
- const result = ok<ExchangeResult>(
1022
+ const result = await confirmed<ExchangeResult>(
963
1023
  await exchangeDeliveredOrderItems.execute(
964
1024
  {
965
1025
  order_id: "#W5866402",
@@ -969,6 +1029,7 @@ describe("exchange_delivered_order_items", () => {
969
1029
  },
970
1030
  ctx,
971
1031
  ),
1032
+ ctx,
972
1033
  );
973
1034
  expect(result.price_difference).toBe(-40.86);
974
1035
  });
@@ -1029,13 +1090,16 @@ describe("exchange_delivered_order_items", () => {
1029
1090
  );
1030
1091
  expect(isToolFailure(pending)).toBe(true);
1031
1092
 
1032
- await exchangeDeliveredOrderItems.execute(
1033
- {
1034
- order_id: "#W4316152",
1035
- item_ids: ["7292993796"],
1036
- new_item_ids: ["3909406921"],
1037
- payment_method_id: "gift_card_7245904",
1038
- },
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
+ ),
1039
1103
  ctx,
1040
1104
  );
1041
1105
  const again = await exchangeDeliveredOrderItems.execute(
@@ -1052,12 +1116,15 @@ describe("exchange_delivered_order_items", () => {
1052
1116
 
1053
1117
  test("a returned order can no longer be exchanged", async () => {
1054
1118
  const ctx = await authedCtx("aarav.anderson9752@example.com");
1055
- await returnDeliveredOrderItems.execute(
1056
- {
1057
- order_id: "#W4316152",
1058
- item_ids: ["7292993796"],
1059
- payment_method_id: "gift_card_7245904",
1060
- },
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
+ ),
1061
1128
  ctx,
1062
1129
  );
1063
1130
  const result = await exchangeDeliveredOrderItems.execute(
@@ -1090,4 +1157,20 @@ describe("transfer_to_human_agents", () => {
1090
1157
  expect(isToolFailure(result)).toBe(false);
1091
1158
  expect(result.transferred).toBe(true);
1092
1159
  });
1160
+
1161
+ test("the handoff is terminal, so the call cannot be worked afterwards", async () => {
1162
+ const ctx = await authedCtx("olivia.ito5204@example.com");
1163
+ ok(await transferToHumanAgents.execute({ summary: "wants a human" }, ctx));
1164
+
1165
+ const at = callFlow.position(ctx);
1166
+ expect(at.state).toBe("transferred");
1167
+ expect(at.done).toBe(true);
1168
+
1169
+ // The policy's "say nothing else after that" used to be enforced by
1170
+ // nothing: every tool stayed callable, so a model that kept going kept
1171
+ // acting on a call it had given away.
1172
+ const refused = await getUserDetails.execute({ user_id: "olivia_ito_3591" }, ctx);
1173
+ expect(isToolFailure(refused)).toBe(true);
1174
+ expect(isToolFailure(refused) && refused.error).toContain('"transferred"');
1175
+ });
1093
1176
  });
@@ -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
  });